diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java index 6d20ec34d93..b9b4704f95b 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -373,10 +373,22 @@ public abstract class AbstractEntityManagerFactoryBean implements else if (method.getDeclaringClass().equals(EntityManagerFactoryPlusOperations.class)) { return method.invoke(this.plusOperations, args); } + else if (method.getName().equals("createEntityManager") && args != null && args.length > 0 && + args[0] != null && args[0].getClass().isEnum() && "SYNCHRONIZED".equals(args[0].toString())) { + // JPA 2.1's createEntityManager(SynchronizationType, Map) + // Redirect to plain createEntityManager and add synchronization semantics through Spring proxy + EntityManager rawEntityManager = (args.length > 1 ? + this.nativeEntityManagerFactory.createEntityManager((Map) args[1]) : + this.nativeEntityManagerFactory.createEntityManager()); + return ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, true); + } + + // Standard delegation to the native factory, just post-processing EntityManager return values Object retVal = method.invoke(this.nativeEntityManagerFactory, args); if (retVal instanceof EntityManager) { + // Any other createEntityManager variant - expecting non-synchronized semantics EntityManager rawEntityManager = (EntityManager) retVal; - retVal = ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this); + retVal = ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, false); } return retVal; } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java index 044cb72be38..2257e000d1c 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java @@ -16,6 +16,7 @@ package org.springframework.orm.jpa; +import java.lang.reflect.Method; import java.util.Map; import javax.persistence.EntityExistsException; import javax.persistence.EntityManager; @@ -45,7 +46,9 @@ import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.transaction.support.ResourceHolderSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** @@ -71,6 +74,25 @@ public abstract class EntityManagerFactoryUtils { private static final Log logger = LogFactory.getLog(EntityManagerFactoryUtils.class); + private static Method createEntityManagerWithSynchronizationTypeMethod; + + private static Object synchronizationTypeUnsynchronized; + + static { + try { + @SuppressWarnings("unchecked") + Class synchronizationTypeClass = (Class) ClassUtils.forName( + "javax.persistence.SynchronizationType", EntityManagerFactoryUtils.class.getClassLoader()); + createEntityManagerWithSynchronizationTypeMethod = EntityManagerFactory.class.getMethod( + "createEntityManager", synchronizationTypeClass, Map.class); + synchronizationTypeUnsynchronized = Enum.valueOf(synchronizationTypeClass, "UNSYNCHRONIZED"); + } + catch (Exception ex) { + // No JPA 2.1 API available + createEntityManagerWithSynchronizationTypeMethod = null; + } + } + /** * Find an EntityManagerFactory with the given name in the given @@ -146,7 +168,7 @@ public abstract class EntityManagerFactoryUtils { public static EntityManager getTransactionalEntityManager(EntityManagerFactory emf, Map properties) throws DataAccessResourceFailureException { try { - return doGetTransactionalEntityManager(emf, properties); + return doGetTransactionalEntityManager(emf, properties, true); } catch (PersistenceException ex) { throw new DataAccessResourceFailureException("Could not obtain JPA EntityManager", ex); @@ -166,53 +188,113 @@ public abstract class EntityManagerFactoryUtils { * @see #getTransactionalEntityManager(javax.persistence.EntityManagerFactory) * @see JpaTransactionManager */ + public static EntityManager doGetTransactionalEntityManager(EntityManagerFactory emf, Map properties) + throws PersistenceException { + + return doGetTransactionalEntityManager(emf, properties, true); + } + + /** + * Obtain a JPA EntityManager from the given factory. Is aware of a + * corresponding EntityManager bound to the current thread, + * for example when using JpaTransactionManager. + *

Same as {@code getEntityManager}, but throwing the original PersistenceException. + * @param emf EntityManagerFactory to create the EntityManager with + * @param properties the properties to be passed into the {@code createEntityManager} + * call (may be {@code null}) + * @param synchronizedWithTransaction whether to automatically join ongoing + * transactions (according to the JPA 2.1 SynchronizationType rules) + * @return the EntityManager, or {@code null} if none found + * @throws javax.persistence.PersistenceException if the EntityManager couldn't be created + * @see #getTransactionalEntityManager(javax.persistence.EntityManagerFactory) + * @see JpaTransactionManager + */ public static EntityManager doGetTransactionalEntityManager( - EntityManagerFactory emf, Map properties) throws PersistenceException { + EntityManagerFactory emf, Map properties, boolean synchronizedWithTransaction) throws PersistenceException { Assert.notNull(emf, "No EntityManagerFactory specified"); EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.getResource(emf); if (emHolder != null) { - if (!emHolder.isSynchronizedWithTransaction() && - TransactionSynchronizationManager.isSynchronizationActive()) { - // Try to explicitly synchronize the EntityManager itself - // with an ongoing JTA transaction, if any. - try { - emHolder.getEntityManager().joinTransaction(); + if (synchronizedWithTransaction) { + if (!emHolder.isSynchronizedWithTransaction() && + TransactionSynchronizationManager.isSynchronizationActive()) { + // Try to explicitly synchronize the EntityManager itself + // with an ongoing JTA transaction, if any. + try { + emHolder.getEntityManager().joinTransaction(); + } + catch (TransactionRequiredException ex) { + logger.debug("Could not join transaction because none was actually active", ex); + } + Object transactionData = prepareTransaction(emHolder.getEntityManager(), emf); + TransactionSynchronizationManager.registerSynchronization( + new TransactionalEntityManagerSynchronization(emHolder, emf, transactionData, false)); + emHolder.setSynchronizedWithTransaction(true); + } + // Use holder's reference count to track synchronizedWithTransaction access. + // isOpen() check used below to find out about it. + emHolder.requested(); + return emHolder.getEntityManager(); + } + else { + // unsynchronized EntityManager demanded + if (emHolder.isTransactionActive() && !emHolder.isOpen()) { + if (!TransactionSynchronizationManager.isSynchronizationActive()) { + return null; + } + // EntityManagerHolder with an active transaction coming from JpaTransactionManager, + // with no synchronized EntityManager having been requested by application code before. + // Unbind in order to register a new unsynchronized EntityManager instead. + TransactionSynchronizationManager.unbindResource(emf); + } + else { + // Either a previously bound unsynchronized EntityManager, or the application + // has requested a synchronized EntityManager before and therefore upgraded + // this transaction's EntityManager to synchronized before. + return emHolder.getEntityManager(); } - catch (TransactionRequiredException ex) { - logger.debug("Could not join JTA transaction because none was active", ex); - } - Object transactionData = prepareTransaction(emHolder.getEntityManager(), emf); - TransactionSynchronizationManager.registerSynchronization( - new EntityManagerSynchronization(emHolder, emf, transactionData, false)); - emHolder.setSynchronizedWithTransaction(true); } - return emHolder.getEntityManager(); } - - if (!TransactionSynchronizationManager.isSynchronizationActive()) { + else if (!TransactionSynchronizationManager.isSynchronizationActive()) { // Indicate that we can't obtain a transactional EntityManager. return null; } // Create a new EntityManager for use within the current transaction. logger.debug("Opening JPA EntityManager"); - EntityManager em = - (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager()); + EntityManager em = null; + if (!synchronizedWithTransaction && createEntityManagerWithSynchronizationTypeMethod != null) { + try { + em = (EntityManager) ReflectionUtils.invokeMethod(createEntityManagerWithSynchronizationTypeMethod, + emf, synchronizationTypeUnsynchronized, properties); + } + catch (AbstractMethodError err) { + // JPA 2.1 API available but method not actually implemented in persistence provider: + // falling back to regular createEntityManager method. + } + } + if (em == null) { + em = (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager()); + } - if (TransactionSynchronizationManager.isSynchronizationActive()) { - logger.debug("Registering transaction synchronization for JPA EntityManager"); - // Use same EntityManager for further JPA actions within the transaction. - // Thread object will get removed by synchronization at transaction completion. - emHolder = new EntityManagerHolder(em); + // Use same EntityManager for further JPA actions within the transaction. + // Thread object will get removed by synchronization at transaction completion. + logger.debug("Registering transaction synchronization for JPA EntityManager"); + emHolder = new EntityManagerHolder(em); + if (synchronizedWithTransaction) { Object transactionData = prepareTransaction(em, emf); TransactionSynchronizationManager.registerSynchronization( - new EntityManagerSynchronization(emHolder, emf, transactionData, true)); + new TransactionalEntityManagerSynchronization(emHolder, emf, transactionData, true)); emHolder.setSynchronizedWithTransaction(true); - TransactionSynchronizationManager.bindResource(emf, emHolder); } + else { + // unsynchronized - just scope it for the transaction, as demanded by the JPA 2.1 spec + TransactionSynchronizationManager.registerSynchronization( + new TransactionScopedEntityManagerSynchronization(emHolder, emf)); + } + TransactionSynchronizationManager.bindResource(emf, emHolder); return em; } @@ -356,7 +438,7 @@ public abstract class EntityManagerFactoryUtils { * (e.g. when participating in a JtaTransactionManager transaction). * @see org.springframework.transaction.jta.JtaTransactionManager */ - private static class EntityManagerSynchronization + private static class TransactionalEntityManagerSynchronization extends ResourceHolderSynchronization implements Ordered { @@ -366,7 +448,7 @@ public abstract class EntityManagerFactoryUtils { private final boolean newEntityManager; - public EntityManagerSynchronization( + public TransactionalEntityManagerSynchronization( EntityManagerHolder emHolder, EntityManagerFactory emf, Object txData, boolean newEm) { super(emHolder, emf); this.transactionData = txData; @@ -381,8 +463,17 @@ public abstract class EntityManagerFactoryUtils { @Override protected void flushResource(EntityManagerHolder resourceHolder) { + EntityManager em = resourceHolder.getEntityManager(); + if (em instanceof EntityManagerProxy) { + EntityManager target = ((EntityManagerProxy) em).getTargetEntityManager(); + if (TransactionSynchronizationManager.hasResource(target)) { + // ExtendedEntityManagerSynchronization active after joinTransaction() call: + // flush synchronization already registered. + return; + } + } try { - resourceHolder.getEntityManager().flush(); + em.flush(); } catch (RuntimeException ex) { if (this.jpaDialect != null) { @@ -415,4 +506,26 @@ public abstract class EntityManagerFactoryUtils { } } + + /** + * Minimal callback that just closes the EntityManager at the end of the transaction. + */ + private static class TransactionScopedEntityManagerSynchronization + extends ResourceHolderSynchronization + implements Ordered { + + public TransactionScopedEntityManagerSynchronization(EntityManagerHolder emHolder, EntityManagerFactory emf) { + super(emHolder, emf); + } + + public int getOrder() { + return ENTITY_MANAGER_SYNCHRONIZATION_ORDER + 1; + } + + @Override + protected void releaseResource(EntityManagerHolder resourceHolder, EntityManagerFactory resourceKey) { + closeEntityManager(resourceHolder.getEntityManager()); + } + } + } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java b/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java index 5f9783d2a17..a94b6bd2498 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -71,7 +71,7 @@ public abstract class ExtendedEntityManagerCreator { public static EntityManager createApplicationManagedEntityManager( EntityManager rawEntityManager, EntityManagerPlusOperations plusOperations) { - return createProxy(rawEntityManager, null, null, plusOperations, null, null, false); + return createProxy(rawEntityManager, null, null, plusOperations, null, null, false, false); } /** @@ -91,7 +91,7 @@ public abstract class ExtendedEntityManagerCreator { EntityManager rawEntityManager, EntityManagerPlusOperations plusOperations, PersistenceExceptionTranslator exceptionTranslator) { - return createProxy(rawEntityManager, null, null, plusOperations, exceptionTranslator, null, false); + return createProxy(rawEntityManager, null, null, plusOperations, exceptionTranslator, null, false, false); } /** @@ -107,13 +107,31 @@ public abstract class ExtendedEntityManagerCreator { public static EntityManager createApplicationManagedEntityManager( EntityManager rawEntityManager, EntityManagerFactoryInfo emfInfo) { - return createProxy(rawEntityManager, emfInfo, false); + return createProxy(rawEntityManager, emfInfo, false, false); + } + + /** + * Create an EntityManager that can join transactions with the + * {@code joinTransaction()} method, but is not automatically + * managed by the container. + * @param rawEntityManager raw EntityManager + * @param emfInfo the EntityManagerFactoryInfo to obtain the + * EntityManagerPlusOperations and PersistenceUnitInfo from + * @param synchronizedWithTransaction whether to automatically join ongoing + * transactions (according to the JPA 2.1 SynchronizationType rules) + * @return an application-managed EntityManager that can join transactions + * but does not participate in them automatically + */ + public static EntityManager createApplicationManagedEntityManager( + EntityManager rawEntityManager, EntityManagerFactoryInfo emfInfo, boolean synchronizedWithTransaction) { + + return createProxy(rawEntityManager, emfInfo, false, synchronizedWithTransaction); } /** - * Create an EntityManager that automatically joins transactions on each - * operation in a transaction. + * Create an EntityManager whose lifecycle is managed by the container and which + * automatically joins a transaction when being invoked within its scope. * @param rawEntityManager raw EntityManager * @param plusOperations an implementation of the EntityManagerPlusOperations * interface, if those operations should be exposed (may be {@code null}) @@ -123,12 +141,12 @@ public abstract class ExtendedEntityManagerCreator { public static EntityManager createContainerManagedEntityManager( EntityManager rawEntityManager, EntityManagerPlusOperations plusOperations) { - return createProxy(rawEntityManager, null, null, plusOperations, null, null, true); + return createProxy(rawEntityManager, null, null, plusOperations, null, null, true, true); } /** - * Create an EntityManager that automatically joins transactions on each - * operation in a transaction. + * Create an EntityManager whose lifecycle is managed by the container and which + * automatically joins a transaction when being invoked within its scope. * @param rawEntityManager raw EntityManager * @param plusOperations an implementation of the EntityManagerPlusOperations * interface, if those operations should be exposed (may be {@code null}) @@ -142,12 +160,12 @@ public abstract class ExtendedEntityManagerCreator { EntityManager rawEntityManager, EntityManagerPlusOperations plusOperations, PersistenceExceptionTranslator exceptionTranslator) { - return createProxy(rawEntityManager, null, null, plusOperations, exceptionTranslator, null, true); + return createProxy(rawEntityManager, null, null, plusOperations, exceptionTranslator, null, true, true); } /** - * Create an EntityManager that automatically joins transactions on each - * operation in a transaction. + * Create an EntityManager whose lifecycle is managed by the container and which + * automatically joins a transaction when being invoked within its scope. * @param rawEntityManager raw EntityManager * @param emfInfo the EntityManagerFactoryInfo to obtain the * EntityManagerPlusOperations and PersistenceUnitInfo from @@ -157,13 +175,12 @@ public abstract class ExtendedEntityManagerCreator { public static EntityManager createContainerManagedEntityManager( EntityManager rawEntityManager, EntityManagerFactoryInfo emfInfo) { - return createProxy(rawEntityManager, emfInfo, true); + return createProxy(rawEntityManager, emfInfo, true, true); } - /** - * Create an EntityManager that automatically joins transactions on each - * operation in a transaction. + * Create an EntityManager whose lifecycle is managed by the container and which + * automatically joins a transaction when being invoked within its scope. * @param emf the EntityManagerFactory to create the EntityManager with. * If this implements the EntityManagerFactoryInfo interface, appropriate handling * of the native EntityManagerFactory and available EntityManagerPlusOperations @@ -173,12 +190,12 @@ public abstract class ExtendedEntityManagerCreator { * @see javax.persistence.EntityManagerFactory#createEntityManager() */ public static EntityManager createContainerManagedEntityManager(EntityManagerFactory emf) { - return createContainerManagedEntityManager(emf, null); + return createContainerManagedEntityManager(emf, null, true); } /** - * Create an EntityManager that automatically joins transactions on each - * operation in a transaction. + * Create an EntityManager whose lifecycle is managed by the container and which + * automatically joins a transaction when being invoked within its scope. * @param emf the EntityManagerFactory to create the EntityManager with. * If this implements the EntityManagerFactoryInfo interface, appropriate handling * of the native EntityManagerFactory and available EntityManagerPlusOperations @@ -190,18 +207,39 @@ public abstract class ExtendedEntityManagerCreator { * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map) */ public static EntityManager createContainerManagedEntityManager(EntityManagerFactory emf, Map properties) { + return createContainerManagedEntityManager(emf, properties, true); + } + + /** + * Create an EntityManager whose lifecycle is managed by the container and which + * may automatically join a transaction when being invoked within its scope. + * @param emf the EntityManagerFactory to create the EntityManager with. + * If this implements the EntityManagerFactoryInfo interface, appropriate handling + * of the native EntityManagerFactory and available EntityManagerPlusOperations + * will automatically apply. + * @param properties the properties to be passed into the {@code createEntityManager} + * call (may be {@code null}) + * @param synchronizedWithTransaction whether to automatically join ongoing + * transactions (according to the JPA 2.1 SynchronizationType rules) + * @return a container-managed EntityManager that expects container-driven lifecycle + * management but may opt out of automatic transaction synchronization + * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map) + */ + public static EntityManager createContainerManagedEntityManager( + EntityManagerFactory emf, Map properties, boolean synchronizedWithTransaction) { + Assert.notNull(emf, "EntityManagerFactory must not be null"); if (emf instanceof EntityManagerFactoryInfo) { EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf; EntityManagerFactory nativeEmf = emfInfo.getNativeEntityManagerFactory(); EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ? nativeEmf.createEntityManager(properties) : nativeEmf.createEntityManager()); - return createProxy(rawEntityManager, emfInfo, true); + return createProxy(rawEntityManager, emfInfo, true, synchronizedWithTransaction); } else { EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager()); - return createProxy(rawEntityManager, null, null, null, null, null, true); + return createProxy(rawEntityManager, null, null, null, null, null, true, synchronizedWithTransaction); } } @@ -213,10 +251,12 @@ public abstract class ExtendedEntityManagerCreator { * EntityManagerPlusOperations and PersistenceUnitInfo from * @param containerManaged whether to follow container-managed EntityManager * or application-managed EntityManager semantics + * @param synchronizedWithTransaction whether to automatically join ongoing + * transactions (according to the JPA 2.1 SynchronizationType rules) * @return the EntityManager proxy */ - private static EntityManager createProxy( - EntityManager rawEntityManager, EntityManagerFactoryInfo emfInfo, boolean containerManaged) { + private static EntityManager createProxy(EntityManager rawEntityManager, + EntityManagerFactoryInfo emfInfo, boolean containerManaged, boolean synchronizedWithTransaction) { Assert.notNull(emfInfo, "EntityManagerFactoryInfo must not be null"); JpaDialect jpaDialect = emfInfo.getJpaDialect(); @@ -227,7 +267,7 @@ public abstract class ExtendedEntityManagerCreator { PersistenceUnitInfo pui = emfInfo.getPersistenceUnitInfo(); Boolean jta = (pui != null ? pui.getTransactionType() == PersistenceUnitTransactionType.JTA : null); return createProxy(rawEntityManager, emfInfo.getEntityManagerInterface(), - emfInfo.getBeanClassLoader(), plusOperations, jpaDialect, jta, containerManaged); + emfInfo.getBeanClassLoader(), plusOperations, jpaDialect, jta, containerManaged, synchronizedWithTransaction); } /** @@ -242,12 +282,14 @@ public abstract class ExtendedEntityManagerCreator { * (or {@code null} if not known in advance) * @param containerManaged whether to follow container-managed EntityManager * or application-managed EntityManager semantics + * @param synchronizedWithTransaction whether to automatically join ongoing + * transactions (according to the JPA 2.1 SynchronizationType rules) * @return the EntityManager proxy */ private static EntityManager createProxy( EntityManager rawEm, Class emIfc, ClassLoader cl, EntityManagerPlusOperations plusOperations, PersistenceExceptionTranslator exceptionTranslator, - Boolean jta, boolean containerManaged) { + Boolean jta, boolean containerManaged, boolean synchronizedWithTransaction) { Assert.notNull(rawEm, "EntityManager must not be null"); Set ifcs = new LinkedHashSet(); @@ -265,7 +307,7 @@ public abstract class ExtendedEntityManagerCreator { (cl != null ? cl : ExtendedEntityManagerCreator.class.getClassLoader()), ifcs.toArray(new Class[ifcs.size()]), new ExtendedEntityManagerInvocationHandler( - rawEm, plusOperations, exceptionTranslator, jta, containerManaged)); + rawEm, plusOperations, exceptionTranslator, jta, containerManaged, synchronizedWithTransaction)); } @@ -283,19 +325,23 @@ public abstract class ExtendedEntityManagerCreator { private final PersistenceExceptionTranslator exceptionTranslator; + private final boolean jta; + private final boolean containerManaged; - private boolean jta; + private final boolean synchronizedWithTransaction; private ExtendedEntityManagerInvocationHandler( EntityManager target, EntityManagerPlusOperations plusOperations, - PersistenceExceptionTranslator exceptionTranslator, Boolean jta, boolean containerManaged) { + PersistenceExceptionTranslator exceptionTranslator, Boolean jta, + boolean containerManaged, boolean synchronizedWithTransaction) { this.target = target; this.plusOperations = plusOperations; this.exceptionTranslator = exceptionTranslator; this.jta = (jta != null ? jta : isJtaEntityManager()); this.containerManaged = containerManaged; + this.synchronizedWithTransaction = synchronizedWithTransaction; } private boolean isJtaEntityManager() { @@ -340,20 +386,33 @@ public abstract class ExtendedEntityManagerCreator { if (this.containerManaged) { throw new IllegalStateException("Invalid usage: Cannot close a container-managed EntityManager"); } + ExtendedEntityManagerSynchronization synch = (ExtendedEntityManagerSynchronization) + TransactionSynchronizationManager.getResource(this.target); + if (synch != null) { + // Local transaction joined - don't actually call close() before transaction completion + synch.closeOnCompletion = true; + return null; + } } else if (method.getName().equals("getTransaction")) { - if (this.containerManaged) { + if (this.synchronizedWithTransaction) { throw new IllegalStateException( - "Cannot execute getTransaction() on a container-managed EntityManager"); + "Cannot obtain local EntityTransaction from a transaction-synchronized EntityManager"); } } else if (method.getName().equals("joinTransaction")) { doJoinTransaction(true); return null; } + else if (method.getName().equals("isJoinedToTransaction")) { + // Handle JPA 2.1 isJoinedToTransaction method for the non-JTA case. + if (!this.jta) { + return TransactionSynchronizationManager.hasResource(this.target); + } + } - // Do automatic joining if required. - if (this.containerManaged && method.getDeclaringClass().isInterface()) { + // Do automatic joining if required. Excludes toString, equals, hashCode calls. + if (this.synchronizedWithTransaction && method.getDeclaringClass().isInterface()) { doJoinTransaction(false); } @@ -416,18 +475,16 @@ public abstract class ExtendedEntityManagerCreator { */ private void enlistInCurrentTransaction() { // Resource local transaction, need to acquire the EntityTransaction, - // start a transaction now and enlist a synchronization for - // commit or rollback later. + // start a transaction now and enlist a synchronization for commit or rollback later. EntityTransaction et = this.target.getTransaction(); et.begin(); if (logger.isDebugEnabled()) { - logger.debug("Starting resource local transaction on application-managed " + + logger.debug("Starting resource-local transaction on application-managed " + "EntityManager [" + this.target + "]"); } ExtendedEntityManagerSynchronization extendedEntityManagerSynchronization = new ExtendedEntityManagerSynchronization(this.target, this.exceptionTranslator); - TransactionSynchronizationManager.bindResource(this.target, - extendedEntityManagerSynchronization); + TransactionSynchronizationManager.bindResource(this.target, extendedEntityManagerSynchronization); TransactionSynchronizationManager.registerSynchronization(extendedEntityManagerSynchronization); } } @@ -445,6 +502,8 @@ public abstract class ExtendedEntityManagerCreator { private final PersistenceExceptionTranslator exceptionTranslator; + public volatile boolean closeOnCompletion = false; + public ExtendedEntityManagerSynchronization( EntityManager em, PersistenceExceptionTranslator exceptionTranslator) { super(new EntityManagerHolder(em), em); @@ -453,7 +512,7 @@ public abstract class ExtendedEntityManagerCreator { } public int getOrder() { - return EntityManagerFactoryUtils.ENTITY_MANAGER_SYNCHRONIZATION_ORDER + 1; + return EntityManagerFactoryUtils.ENTITY_MANAGER_SYNCHRONIZATION_ORDER - 1; } @Override @@ -485,14 +544,21 @@ public abstract class ExtendedEntityManagerCreator { @Override public void afterCompletion(int status) { - super.afterCompletion(status); - if (status != STATUS_COMMITTED) { - // Haven't had an afterCommit call: trigger a rollback. - try { - this.entityManager.getTransaction().rollback(); + try { + super.afterCompletion(status); + if (status != STATUS_COMMITTED) { + // Haven't had an afterCommit call: trigger a rollback. + try { + this.entityManager.getTransaction().rollback(); + } + catch (RuntimeException ex) { + throw convertException(ex); + } } - catch (RuntimeException ex) { - throw convertException(ex); + } + finally { + if (this.closeOnCompletion) { + EntityManagerFactoryUtils.closeEntityManager(this.entityManager); } } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaDialect.java b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaDialect.java index a75fb9f85af..882fa835239 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaDialect.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaDialect.java @@ -17,7 +17,6 @@ package org.springframework.orm.jpa; import java.sql.SQLException; - import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceException; @@ -135,13 +134,15 @@ public interface JpaDialect extends PersistenceExceptionTranslator { /** * Prepare a JPA transaction, applying the specified semantics. Called by - * EntityManagerFactoryUtils when enlisting an EntityManager in a JTA transaction. + * EntityManagerFactoryUtils when enlisting an EntityManager in a JTA transaction + * or a locally joined transaction (e.g. after upgrading an unsynchronized + * EntityManager to a synchronized one). *

An implementation can apply the read-only flag as flush mode. In that case, * a transaction data object can be returned that holds the previous flush mode * (and possibly other data), to be reset in {@code cleanupTransaction}. - *

Implementations can also use the Spring transaction name, as exposed by the - * passed-in TransactionDefinition, to optimize for specific data access use cases - * (effectively using the current transaction name as use case identifier). + *

Implementations can also use the Spring transaction name to optimize for + * specific data access use cases (effectively using the current transaction + * name as use case identifier). * @param entityManager the EntityManager to begin a JPA transaction on * @param readOnly whether the transaction is supposed to be read-only * @param name the name of the transaction (if any) diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java index 259c6e37567..8500c7d8718 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -566,9 +566,11 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager protected void doCleanupAfterCompletion(Object transaction) { JpaTransactionObject txObject = (JpaTransactionObject) transaction; - // Remove the entity manager holder from the thread. + // Remove the entity manager holder from the thread, if still there. + // (Could have been removed by EntityManagerFactoryUtils in order + // to replace it with an unsynchronized EntityManager). if (txObject.isNewEntityManagerHolder()) { - TransactionSynchronizationManager.unbindResource(getEntityManagerFactory()); + TransactionSynchronizationManager.unbindResourceIfPossible(getEntityManagerFactory()); } txObject.getEntityManagerHolder().clear(); 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 00f15cfa4cf..d88b5a9d14f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -53,7 +53,8 @@ import org.springframework.util.CollectionUtils; public abstract class SharedEntityManagerCreator { /** - * Create a transactional EntityManager proxy for the given EntityManagerFactory. + * Create a transactional EntityManager proxy for the given EntityManagerFactory, + * automatically joining ongoing transactions. * @param emf the EntityManagerFactory to delegate to. * If this implements the {@link EntityManagerFactoryInfo} interface, * appropriate handling of the native EntityManagerFactory and available @@ -61,7 +62,7 @@ public abstract class SharedEntityManagerCreator { * @return a shareable transaction EntityManager proxy */ public static EntityManager createSharedEntityManager(EntityManagerFactory emf) { - return createSharedEntityManager(emf, null); + return createSharedEntityManager(emf, null, true); } /** @@ -75,6 +76,24 @@ public abstract class SharedEntityManagerCreator { * @return a shareable transaction EntityManager proxy */ public static EntityManager createSharedEntityManager(EntityManagerFactory emf, Map properties) { + return createSharedEntityManager(emf, properties, true); + } + + /** + * Create a transactional EntityManager proxy for the given EntityManagerFactory. + * @param emf the EntityManagerFactory to delegate to. + * If this implements the {@link EntityManagerFactoryInfo} interface, + * appropriate handling of the native EntityManagerFactory and available + * {@link EntityManagerPlusOperations} will automatically apply. + * @param properties the properties to be passed into the + * {@code createEntityManager} call (may be {@code null}) + * @param synchronizedWithTransaction whether to automatically join ongoing + * transactions (according to the JPA 2.1 SynchronizationType rules) + * @return a shareable transaction EntityManager proxy + */ + public static EntityManager createSharedEntityManager( + EntityManagerFactory emf, Map properties, boolean synchronizedWithTransaction) { + Class[] emIfcs; if (emf instanceof EntityManagerFactoryInfo) { EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf; @@ -93,7 +112,7 @@ public abstract class SharedEntityManagerCreator { else { emIfcs = new Class[] {EntityManager.class}; } - return createSharedEntityManager(emf, properties, emIfcs); + return createSharedEntityManager(emf, properties, synchronizedWithTransaction, emIfcs); } /** @@ -108,6 +127,23 @@ public abstract class SharedEntityManagerCreator { public static EntityManager createSharedEntityManager( EntityManagerFactory emf, Map properties, Class... entityManagerInterfaces) { + return createSharedEntityManager(emf, properties, true, entityManagerInterfaces); + } + + /** + * Create a transactional EntityManager proxy for the given EntityManagerFactory. + * @param emf EntityManagerFactory to obtain EntityManagers from as needed + * @param properties the properties to be passed into the + * {@code createEntityManager} call (may be {@code null}) + * @param synchronizedWithTransaction whether to automatically join ongoing + * transactions (according to the JPA 2.1 SynchronizationType rules) + * @param entityManagerInterfaces the interfaces to be implemented by the + * EntityManager. Allows the addition or specification of proprietary interfaces. + * @return a shareable transactional EntityManager proxy + */ + public static EntityManager createSharedEntityManager(EntityManagerFactory emf, Map properties, + boolean synchronizedWithTransaction, Class... entityManagerInterfaces) { + ClassLoader cl = null; if (emf instanceof EntityManagerFactoryInfo) { cl = ((EntityManagerFactoryInfo) emf).getBeanClassLoader(); @@ -117,7 +153,7 @@ public abstract class SharedEntityManagerCreator { ifcs[entityManagerInterfaces.length] = EntityManagerProxy.class; return (EntityManager) Proxy.newProxyInstance( (cl != null ? cl : SharedEntityManagerCreator.class.getClassLoader()), - ifcs, new SharedEntityManagerInvocationHandler(emf, properties)); + ifcs, new SharedEntityManagerInvocationHandler(emf, properties, synchronizedWithTransaction)); } @@ -135,11 +171,15 @@ public abstract class SharedEntityManagerCreator { private final Map properties; + private final boolean synchronizedWithTransaction; + private transient volatile ClassLoader proxyClassLoader; - public SharedEntityManagerInvocationHandler(EntityManagerFactory target, Map properties) { + public SharedEntityManagerInvocationHandler( + EntityManagerFactory target, Map properties, boolean synchronizedWithTransaction) { this.targetFactory = target; this.properties = properties; + this.synchronizedWithTransaction = synchronizedWithTransaction; initProxyClassLoader(); } @@ -200,16 +240,11 @@ public abstract class SharedEntityManagerCreator { "Not allowed to create transaction on shared EntityManager - " + "use Spring transactions or EJB CMT instead"); } - else if (method.getName().equals("joinTransaction")) { - throw new IllegalStateException( - "Not allowed to join transaction on shared EntityManager - " + - "use Spring transactions or EJB CMT instead"); - } // Determine current EntityManager: either the transactional one // managed by the factory or a temporary one for the given invocation. - EntityManager target = - EntityManagerFactoryUtils.doGetTransactionalEntityManager(this.targetFactory, this.properties); + EntityManager target = EntityManagerFactoryUtils.doGetTransactionalEntityManager( + this.targetFactory, this.properties, this.synchronizedWithTransaction); if (method.getName().equals("getTargetEntityManager")) { // Handle EntityManagerProxy interface. diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java index 39c437e8cdf..97a412ede3d 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java @@ -61,6 +61,7 @@ import org.springframework.orm.jpa.ExtendedEntityManagerCreator; import org.springframework.orm.jpa.SharedEntityManagerCreator; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; /** * BeanPostProcessor that processes {@link javax.persistence.PersistenceUnit} @@ -165,6 +166,11 @@ public class PersistenceAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware, Serializable { + /* Check JPA 2.1 PersistenceContext.synchronizationType attribute */ + private static final Method synchronizationTypeAttribute = + ClassUtils.getMethodIfAvailable(PersistenceContext.class, "synchronizationType"); + + private Object jndiEnvironment; private boolean resourceRef = true; @@ -589,6 +595,8 @@ public class PersistenceAnnotationBeanPostProcessor private PersistenceContextType type; + private boolean synchronizedWithTransaction = false; + private Properties properties; public PersistenceElement(Member member, PropertyDescriptor pd) { @@ -612,6 +620,8 @@ public class PersistenceAnnotationBeanPostProcessor } this.unitName = pc.unitName(); this.type = pc.type(); + this.synchronizedWithTransaction = (synchronizationTypeAttribute == null || + "SYNCHRONIZED".equals(ReflectionUtils.invokeMethod(synchronizationTypeAttribute, pc).toString())); this.properties = properties; } else { @@ -664,11 +674,13 @@ public class PersistenceAnnotationBeanPostProcessor ((EntityManagerFactoryInfo) emf).getEntityManagerInterface() != null) { // Create EntityManager based on the info's vendor-specific type // (which might be more specific than the field's type). - em = SharedEntityManagerCreator.createSharedEntityManager(emf, this.properties); + em = SharedEntityManagerCreator.createSharedEntityManager( + emf, this.properties, this.synchronizedWithTransaction); } else { // Create EntityManager based on the field's type. - em = SharedEntityManagerCreator.createSharedEntityManager(emf, this.properties, getResourceType()); + em = SharedEntityManagerCreator.createSharedEntityManager( + emf, this.properties, this.synchronizedWithTransaction, getResourceType()); } } return em; @@ -686,7 +698,8 @@ public class PersistenceAnnotationBeanPostProcessor emf = findEntityManagerFactory(this.unitName, requestingBeanName); } // Inject a container-managed extended EntityManager. - em = ExtendedEntityManagerCreator.createContainerManagedEntityManager(emf, this.properties); + em = ExtendedEntityManagerCreator.createContainerManagedEntityManager( + emf, this.properties, this.synchronizedWithTransaction); } if (em instanceof EntityManagerProxy && beanFactory != null && !beanFactory.isPrototype(requestingBeanName)) { diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/SharedEntityManagerBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/SharedEntityManagerBean.java index 14864f4d0dc..30d40325e03 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/SharedEntityManagerBean.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/SharedEntityManagerBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -55,6 +55,8 @@ public class SharedEntityManagerBean extends EntityManagerFactoryAccessor private Class entityManagerInterface; + private boolean synchronizedWithTransaction = true; + private EntityManager shared; @@ -72,6 +74,14 @@ public class SharedEntityManagerBean extends EntityManagerFactoryAccessor this.entityManagerInterface = entityManagerInterface; } + /** + * Set whether to automatically join ongoing transactions (according + * to the JPA 2.1 SynchronizationType rules). Default is "true". + */ + public void setSynchronizedWithTransaction(boolean synchronizedWithTransaction) { + this.synchronizedWithTransaction = synchronizedWithTransaction; + } + public final void afterPropertiesSet() { EntityManagerFactory emf = getEntityManagerFactory(); @@ -101,7 +111,8 @@ public class SharedEntityManagerBean extends EntityManagerFactoryAccessor } ifcs = new Class[] {this.entityManagerInterface}; } - this.shared = SharedEntityManagerCreator.createSharedEntityManager(emf, getJpaPropertyMap(), ifcs); + this.shared = SharedEntityManagerCreator.createSharedEntityManager( + emf, getJpaPropertyMap(), this.synchronizedWithTransaction, ifcs); } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java index c4ae64b3c86..2718bcc4e49 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java @@ -156,15 +156,6 @@ public abstract class AbstractContainerEntityManagerFactoryIntegrationTests } } - public void testSharedEntityManagerProxyRejectsProgrammaticTxJoining() { - try { - sharedEntityManager.joinTransaction(); - fail("Should not be able to join transactions with container managed EntityManager"); - } - catch (IllegalStateException ex) { - } - } - // public void testAspectJInjectionOfConfigurableEntity() { // Person p = new Person(); // System.err.println(p); diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java index 7d035b05a04..4db22f30bf9 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java @@ -323,6 +323,16 @@ public class LocalContainerEntityManagerFactoryBeanTests extends AbstractEntityM public ProviderUtil getProviderUtil() { throw new UnsupportedOperationException(); } + + @Override + public void generateSchema(PersistenceUnitInfo persistenceUnitInfo, Map map) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean generateSchema(String persistenceUnitName, Map map) { + throw new UnsupportedOperationException(); + } } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java index afe4572b044..4e2358497d8 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java @@ -100,6 +100,16 @@ public class LocalEntityManagerFactoryBeanTests extends AbstractEntityManagerFac public ProviderUtil getProviderUtil() { throw new UnsupportedOperationException(); } + + @Override + public void generateSchema(PersistenceUnitInfo persistenceUnitInfo, Map map) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean generateSchema(String persistenceUnitName, Map map) { + throw new UnsupportedOperationException(); + } } }