diff --git a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java index 754764f890..5a2e8bd016 100644 --- a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java @@ -68,17 +68,29 @@ public interface SharedSessionContract extends QueryProducer, AutoCloseable, Ser * Begin a unit of work and return the associated {@link Transaction} object. * If a new underlying transaction is required, begin the transaction. Otherwise, * continue the new work in the context of the existing underlying transaction. - *

- * The JPA-standard way to begin a new transaction is by calling - * {@link #getTransaction getTransaction().begin()}. When + * + * @apiNote + * The JPA-standard way to begin a new resource-local transaction is by calling + * {@link #getTransaction getTransaction().begin()}. But it's not always safe to + * execute this idiom. + *

* * @return an instance of {@link Transaction} representing the new transaction * @@ -90,6 +102,20 @@ public interface SharedSessionContract extends QueryProducer, AutoCloseable, Ser /** * Get the {@link Transaction} instance associated with this session. * + * @apiNote + * This method is the JPA-standard way to obtain an instance of + * {@link jakarta.persistence.EntityTransaction EntityTransaction} + * representing a resource-local transaction. But JPA doesn't allow an + * {@code EntityTransaction} to represent a JTA transaction. Therefore, when + * {@linkplain org.hibernate.jpa.spi.JpaCompliance#isJpaTransactionComplianceEnabled + * strict JPA transaction compliance} is enabled via, for example, setting + * {@value org.hibernate.cfg.JpaComplianceSettings#JPA_TRANSACTION_COMPLIANCE}, + * this method fails if transactions are managed by JTA. + *

+ * On the other hand, when JTA transaction management is used, and when + * strict JPA transaction compliance is disabled, this method happily + * returns a {@link Transaction} representing the current JTA transaction context. + * * @return an instance of {@link Transaction} representing the transaction * associated with this session * diff --git a/hibernate-core/src/main/java/org/hibernate/Transaction.java b/hibernate-core/src/main/java/org/hibernate/Transaction.java index 0ce4b4f037..c6d9e4b2af 100644 --- a/hibernate-core/src/main/java/org/hibernate/Transaction.java +++ b/hibernate-core/src/main/java/org/hibernate/Transaction.java @@ -17,7 +17,7 @@ import org.hibernate.resource.transaction.spi.TransactionStatus; * depending on how Hibernate is configured. *

* Every resource-local transaction is associated with a {@link Session} and begins with - * an explicit call to {@link Session#beginTransaction()}, or, equivalently, with + * an explicit call to {@link Session#beginTransaction()}, or, almost equivalently, with * {@code session.getTransaction().begin()}, and ends with a call to {@link #commit()} * or {@link #rollback()}. *

@@ -31,6 +31,11 @@ import org.hibernate.resource.transaction.spi.TransactionStatus; *

* A {@code Transaction} object is not threadsafe. * + * @apiNote JPA doesn't allow an {@link EntityTransaction} to represent a JTA transaction. + * But when {@linkplain org.hibernate.jpa.spi.JpaCompliance#isJpaTransactionComplianceEnabled + * strict JPA transaction compliance} is disabled, as it is by default, Hibernate allows an + * instance of this interface to represent the current JTA transaction context. + * * @author Anton van Straaten * @author Steve Ebersole * diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index 1e5de58aa4..a59c24dbc4 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -610,7 +610,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont @Override public Transaction beginTransaction() { checkOpen(); - final Transaction transaction = getTransaction(); + final Transaction transaction = accessTransaction(); // only need to begin a transaction if it was not // already active (this is the documented semantics) if ( !transaction.isActive() ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/resource/transaction/jta/JpaComplianceAlreadyStartedTransactionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/resource/transaction/jta/JpaComplianceAlreadyStartedTransactionTest.java index f0ca7aabf9..8e7bc38d9d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/resource/transaction/jta/JpaComplianceAlreadyStartedTransactionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/resource/transaction/jta/JpaComplianceAlreadyStartedTransactionTest.java @@ -45,7 +45,8 @@ public class JpaComplianceAlreadyStartedTransactionTest extends BaseNonConfigCor Transaction tx = null; try { // A call to begin() with an active Tx should cause an IllegalStateException - tx = s.beginTransaction(); + tx = s.getTransaction(); + tx.begin(); } catch (Exception e) { if ( tx != null && tx.isActive() ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/resource/transaction/jta/NonJpaComplianceAlreadyStartedTransactionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/resource/transaction/jta/NonJpaComplianceAlreadyStartedTransactionTest.java index a0e51a4368..5b27adf226 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/resource/transaction/jta/NonJpaComplianceAlreadyStartedTransactionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/resource/transaction/jta/NonJpaComplianceAlreadyStartedTransactionTest.java @@ -50,7 +50,8 @@ public class NonJpaComplianceAlreadyStartedTransactionTest extends BaseNonConfig public void noIllegalStateExceptionShouldBeThrownWhenBeginTxIsCalledWithAnAlreadyActiveTx() throws Exception { tm.begin(); try (Session s = openSession()) { - Transaction tx = s.beginTransaction(); + Transaction tx = s.getTransaction(); + tx.begin(); try { s.persist( new TestEntity( "ABC" ) ); tx.commit();