From c444e8ff746c1f52366a9de12139716b66230bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9da=20Housni=20Alaoui?= Date: Tue, 25 Oct 2022 12:08:53 +0200 Subject: [PATCH] SharedEntityManagerCreator is oblivious to the presence of multiple TransactionManager --- .../orm/jpa/EntityManagerFactoryUtils.java | 26 ++++++++++++++++++ .../orm/jpa/SharedEntityManagerCreator.java | 6 ++--- ...iEntityManagerFactoryIntegrationTests.java | 27 +++++++++++++++++-- .../jpa/hibernate/hibernate-manager-multi.xml | 4 +++ 4 files changed, 58 insertions(+), 5 deletions(-) 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 791ac10b6922..dc1bd93636d6 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 @@ -63,6 +63,7 @@ *

Mainly intended for internal use within the framework. * * @author Juergen Hoeller + * @author Réda Housni Alaoui * @since 2.0 */ public abstract class EntityManagerFactoryUtils { @@ -196,6 +197,28 @@ public static EntityManager doGetTransactionalEntityManager(EntityManagerFactory public static EntityManager doGetTransactionalEntityManager( EntityManagerFactory emf, @Nullable Map properties, boolean synchronizedWithTransaction) throws PersistenceException { + return doGetTransactionalEntityManager(emf, properties, synchronizedWithTransaction, true); + } + + /** + * Obtain a JPA EntityManager from the given factory. Is aware of a corresponding + * EntityManager bound to the current thread, e.g. when using JpaTransactionManager. + *

Same as {@code getEntityManager}, but throwing the original PersistenceException. + * @param emf the 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) + * @param createIfNeeded whether to create an {@link EntityManager} if no existing transactional one is found + * @return the EntityManager, or {@code null} if none found + * @throws jakarta.persistence.PersistenceException if the EntityManager couldn't be created + * @see #getTransactionalEntityManager(jakarta.persistence.EntityManagerFactory) + * @see JpaTransactionManager + */ + @Nullable + public static EntityManager doGetTransactionalEntityManager( + EntityManagerFactory emf, @Nullable Map properties, boolean synchronizedWithTransaction, boolean createIfNeeded) + throws PersistenceException { Assert.notNull(emf, "No EntityManagerFactory specified"); @@ -249,6 +272,9 @@ else if (!TransactionSynchronizationManager.isSynchronizationActive()) { // Indicate that we can't obtain a transactional EntityManager. return null; } + else if (!createIfNeeded) { + return null; + } // Create a new EntityManager for use within the current transaction. logger.debug("Opening JPA EntityManager"); 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 911bbf3ba330..c5deab89de45 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 @@ -61,6 +61,7 @@ * @author Oliver Gierke * @author Mark Paluch * @author Sam Brannen + * @author Réda Housni Alaoui * @since 2.0 * @see jakarta.persistence.PersistenceContext * @see jakarta.persistence.PersistenceContextType#TRANSACTION @@ -260,10 +261,9 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl "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. + // Determine current EntityManager EntityManager target = EntityManagerFactoryUtils.doGetTransactionalEntityManager( - this.targetFactory, this.properties, this.synchronizedWithTransaction); + this.targetFactory, this.properties, this.synchronizedWithTransaction, false); switch (method.getName()) { case "getTargetEntityManager" -> { diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateMultiEntityManagerFactoryIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateMultiEntityManagerFactoryIntegrationTests.java index 2a959616964c..174ccacd0be3 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateMultiEntityManagerFactoryIntegrationTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateMultiEntityManagerFactoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -23,24 +23,32 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.jpa.AbstractContainerEntityManagerFactoryIntegrationTests; import org.springframework.orm.jpa.EntityManagerFactoryInfo; +import org.springframework.orm.jpa.domain.Person; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * Hibernate-specific JPA tests with multiple EntityManagerFactory instances. * * @author Juergen Hoeller + * @author Réda Housni Alaoui */ public class HibernateMultiEntityManagerFactoryIntegrationTests extends AbstractContainerEntityManagerFactoryIntegrationTests { @Autowired private EntityManagerFactory entityManagerFactory2; + @Autowired + private PlatformTransactionManager transactionManager2; + @Override protected String[] getConfigLocations() { - return new String[] {"/org/springframework/orm/jpa/hibernate/hibernate-manager-multi.xml", + return new String[]{"/org/springframework/orm/jpa/hibernate/hibernate-manager-multi.xml", "/org/springframework/orm/jpa/memdb.xml"}; } @@ -68,4 +76,19 @@ public void testEntityManagerFactory2() { } } + @Test + public void testInstantiateAndSaveWithSharedEmProxyUnderTheWrongTransaction() { + endTransaction(); + TransactionStatus transaction = this.transactionManager2.getTransaction(this.transactionDefinition); + + assertThat(countRowsInTable("person")).as("Should be no people from previous transactions").isEqualTo(0); + Person person = new Person(); + person.setFirstName("Tony"); + person.setLastName("Blair"); + assertThatThrownBy(() -> sharedEntityManager.persist(person)) + .hasMessageContaining("No EntityManager with actual transaction available for current thread"); + + transactionManager2.rollback(transaction); + } + } diff --git a/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager-multi.xml b/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager-multi.xml index fc0a64c4ee76..eeb5c003f315 100644 --- a/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager-multi.xml +++ b/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager-multi.xml @@ -27,4 +27,8 @@ + + + +