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 9d78606b86c..f31be910790 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 @@ -387,15 +387,11 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager JpaTransactionObject txObject = (JpaTransactionObject) transaction; if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) { - throw new IllegalTransactionStateException( - "Pre-bound JDBC Connection found! JpaTransactionManager does not support " + - "running within another transaction manager that exposes a JDBC Connection " + - "for the same DataSource (for example, a JpaTransactionManager or " + - "HibernateTransactionManager for a different persistence unit) if told to " + - "manage the DataSource itself. Alternatively, a JDBC Connection may have been " + - "left bound to the current thread. It is recommended to use a single " + - "JpaTransactionManager for all transactions on a single DataSource, " + - "no matter whether JPA or JDBC access."); + throw new IllegalTransactionStateException(""" + Pre-bound JDBC Connection found! JpaTransactionManager does not support running within \ + another transaction manager that exposes a JDBC Connection for the same DataSource \ + (or after a leaked Connection binding) if told to manage the DataSource itself. \ + Use a single JpaTransactionManager for all transactions on a single DataSource."""); } try { diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManager.java index 3da04a23719..856c26c4067 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManager.java @@ -443,15 +443,11 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) { - throw new IllegalTransactionStateException( - "Pre-bound JDBC Connection found! HibernateTransactionManager does not support " + - "running within another transaction manager that exposes a JDBC Connection " + - "for the same DataSource (for example, a HibernateTransactionManager or " + - "JpaTransactionManager for a different SessionFactory) if told to manage " + - "the DataSource itself. Alternatively, a JDBC Connection may have been " + - "left bound to the current thread. It is recommended to use a single " + - "HibernateTransactionManager for all transactions on a single DataSource, " + - "no matter whether Hibernate or JDBC access."); + throw new IllegalTransactionStateException(""" + Pre-bound JDBC Connection found! HibernateTransactionManager does not support running within \ + another transaction manager that exposes a JDBC Connection for the same DataSource \ + (or after a leaked Connection binding) if told to manage the DataSource itself. \ + Use a single HibernateTransactionManager for all transactions on a single DataSource."""); } SessionImplementor session = null; diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/JpaTransactionManagerTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/JpaTransactionManagerTests.java index a6147daf0a2..41a77a7fa61 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/JpaTransactionManagerTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/JpaTransactionManagerTests.java @@ -50,6 +50,7 @@ import static org.mockito.Mockito.verify; /** * Unit tests for {@link JpaTransactionManager}. + * * @author Costin Leau * @author Juergen Hoeller * @author Phillip Webb @@ -619,7 +620,7 @@ class JpaTransactionManagerTests { verify(manager).close(); } - @Test // gh-14130 + @Test // gh-37356 void transactionWithinOtherTransactionManagerForSameDataSource() { DataSource dataSource = mock(); Connection connection = mock(); @@ -641,9 +642,10 @@ class JpaTransactionManagerTests { otherTm.setJpaDialect(jpaDialect); tm.setDataSource(dataSource); tm.setJpaDialect(jpaDialect); + TransactionTemplate otherTt = new TransactionTemplate(otherTm); assertThatExceptionOfType(IllegalTransactionStateException.class) - .isThrownBy(() -> new TransactionTemplate(otherTm).executeWithoutResult(status -> + .isThrownBy(() -> otherTt.executeWithoutResult(status -> tt.executeWithoutResult(innerStatus -> {}))) .withMessageStartingWith("Pre-bound JDBC Connection found!") .withMessageContaining("another transaction manager") diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManagerTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManagerTests.java new file mode 100644 index 00000000000..abf7f43de50 --- /dev/null +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManagerTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-present 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.jpa.hibernate; + +import java.sql.Connection; + +import javax.sql.DataSource; + +import org.hibernate.SessionFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import org.springframework.jdbc.datasource.ConnectionHolder; +import org.springframework.transaction.IllegalTransactionStateException; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.transaction.support.TransactionTemplate; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.mock; + +/** + * Unit tests for {@link HibernateTransactionManager}. + * + * @author Sam Brannen + * @since 7.1 + */ +class HibernateTransactionManagerTests { + + private final DataSource dataSource = mock(); + + + @AfterEach + void unbindDataSource() { + if (TransactionSynchronizationManager.hasResource(this.dataSource)) { + TransactionSynchronizationManager.unbindResource(this.dataSource); + } + } + + + @Test // gh-37356 + void transactionWithinOtherTransactionManagerForSameDataSource() { + HibernateTransactionManager tm = new HibernateTransactionManager(mock(SessionFactory.class)); + tm.setAutodetectDataSource(false); + tm.setDataSource(this.dataSource); + TransactionTemplate transactionTemplate = new TransactionTemplate(tm); + + // Simulate a JDBC Connection exposed by another transaction manager for the same DataSource. + TransactionSynchronizationManager.bindResource(this.dataSource, new ConnectionHolder(mock(Connection.class))); + + assertThatExceptionOfType(IllegalTransactionStateException.class) + .isThrownBy(() -> transactionTemplate.executeWithoutResult(status -> {})) + .withMessageStartingWith("Pre-bound JDBC Connection found!") + .withMessageContaining("another transaction manager") + .withMessageNotContaining("DataSourceTransactionManager"); + } + +}