diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java b/spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java index 88352be4948..4219f75647f 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java @@ -23,10 +23,13 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Query; +import javax.persistence.TransactionRequiredException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -61,6 +64,23 @@ public abstract class SharedEntityManagerCreator { private static final Class[] NO_ENTITY_MANAGER_INTERFACES = new Class[0]; + private static final Set transactionRequiringMethods = new HashSet(6); + + private static final Set queryTerminationMethods = new HashSet(3); + + static { + transactionRequiringMethods.add("joinTransaction"); + transactionRequiringMethods.add("flush"); + transactionRequiringMethods.add("persist"); + transactionRequiringMethods.add("merge"); + transactionRequiringMethods.add("remove"); + transactionRequiringMethods.add("refresh"); + + queryTerminationMethods.add("getResultList"); + queryTerminationMethods.add("getSingleResult"); + queryTerminationMethods.add("executeUpdate"); + } + /** * Create a transactional EntityManager proxy for the given EntityManagerFactory. @@ -246,6 +266,13 @@ public abstract class SharedEntityManagerCreator { } // Still perform unwrap call on target EntityManager. } + else if (transactionRequiringMethods.contains(method.getName())) { + // We need a transactional target now, according to the JPA spec. + // Otherwise, the operation would get accepted but remain unflushed... + if (target == null) { + throw new TransactionRequiredException("No transactional EntityManager available"); + } + } // Regular EntityManager operations. boolean isNewEm = false; @@ -337,8 +364,7 @@ public abstract class SharedEntityManagerCreator { throw ex.getTargetException(); } finally { - if (method.getName().equals("getResultList") || method.getName().equals("getSingleResult") || - method.getName().equals("executeUpdate")) { + if (queryTerminationMethods.contains(method.getName())) { // Actual execution of the query: close the EntityManager right // afterwards, since that was the only reason we kept it open. EntityManagerFactoryUtils.closeEntityManager(this.em); diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/SharedEntityManagerCreatorTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/SharedEntityManagerCreatorTests.java index 77aa6f2fc86..8b1de7680b0 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/SharedEntityManagerCreatorTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/SharedEntityManagerCreatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,9 @@ package org.springframework.orm.jpa; +import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; +import javax.persistence.TransactionRequiredException; import org.junit.Test; @@ -40,4 +42,46 @@ public class SharedEntityManagerCreatorTests { is(notNullValue())); } + @Test(expected = TransactionRequiredException.class) + public void transactionRequiredExceptionOnJoinTransaction() { + EntityManagerFactory emf = mock(EntityManagerFactory.class); + EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf); + em.joinTransaction(); + } + + @Test(expected = TransactionRequiredException.class) + public void transactionRequiredExceptionOnFlush() { + EntityManagerFactory emf = mock(EntityManagerFactory.class); + EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf); + em.flush(); + } + + @Test(expected = TransactionRequiredException.class) + public void transactionRequiredExceptionOnPersist() { + EntityManagerFactory emf = mock(EntityManagerFactory.class); + EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf); + em.persist(new Object()); + } + + @Test(expected = TransactionRequiredException.class) + public void transactionRequiredExceptionOnMerge() { + EntityManagerFactory emf = mock(EntityManagerFactory.class); + EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf); + em.merge(new Object()); + } + + @Test(expected = TransactionRequiredException.class) + public void transactionRequiredExceptionOnRemove() { + EntityManagerFactory emf = mock(EntityManagerFactory.class); + EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf); + em.remove(new Object()); + } + + @Test(expected = TransactionRequiredException.class) + public void transactionRequiredExceptionOnRefresh() { + EntityManagerFactory emf = mock(EntityManagerFactory.class); + EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf); + em.refresh(new Object()); + } + }