Skip to content

Commit f34c69c

Browse files
committed
HHH-9927 - Explicit calls to EntityManager.joinTransaction() with no active JTA transaction should throw a TransactionRequiredException
1 parent 289e59a commit f34c69c

File tree

7 files changed

+101
-45
lines changed

7 files changed

+101
-45
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.resource.transaction;
8+
9+
import org.hibernate.HibernateException;
10+
11+
/**
12+
* Indicates a call to {@link TransactionCoordinator#explicitJoin()} that requires an
13+
* active transaction where there currently is none.
14+
*
15+
* @author Steve Ebersole
16+
*/
17+
public class TransactionRequiredForJoinException extends HibernateException {
18+
public TransactionRequiredForJoinException(String message) {
19+
super( message );
20+
}
21+
22+
public TransactionRequiredForJoinException(String message, Throwable cause) {
23+
super( message, cause );
24+
}
25+
}

hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionCoordinatorImpl.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66
*/
77
package org.hibernate.resource.transaction.backend.jta.internal;
88

9-
import javax.transaction.Status;
10-
import javax.transaction.TransactionManager;
11-
import javax.transaction.UserTransaction;
129
import java.util.ArrayList;
1310
import java.util.Collections;
1411
import java.util.List;
15-
16-
import org.jboss.logging.Logger;
12+
import javax.transaction.Status;
13+
import javax.transaction.TransactionManager;
14+
import javax.transaction.UserTransaction;
1715

1816
import org.hibernate.HibernateException;
1917
import org.hibernate.boot.spi.SessionFactoryOptions;
@@ -26,6 +24,7 @@
2624
import org.hibernate.resource.transaction.SynchronizationRegistry;
2725
import org.hibernate.resource.transaction.TransactionCoordinator;
2826
import org.hibernate.resource.transaction.TransactionCoordinatorBuilder;
27+
import org.hibernate.resource.transaction.TransactionRequiredForJoinException;
2928
import org.hibernate.resource.transaction.backend.jta.internal.synchronization.RegisteredSynchronization;
3029
import org.hibernate.resource.transaction.backend.jta.internal.synchronization.SynchronizationCallbackCoordinator;
3130
import org.hibernate.resource.transaction.backend.jta.internal.synchronization.SynchronizationCallbackCoordinatorNonTrackingImpl;
@@ -35,6 +34,8 @@
3534
import org.hibernate.resource.transaction.spi.TransactionCoordinatorOwner;
3635
import org.hibernate.resource.transaction.spi.TransactionStatus;
3736

37+
import org.jboss.logging.Logger;
38+
3839
import static org.hibernate.internal.CoreLogging.logger;
3940

4041
/**
@@ -168,7 +169,9 @@ public void explicitJoin() {
168169
}
169170

170171
if ( getTransactionDriverControl().getStatus() != TransactionStatus.ACTIVE ) {
171-
return;
172+
throw new TransactionRequiredForJoinException(
173+
"Explicitly joining a JTA transaction requires a JTA transaction be currently active"
174+
);
172175
}
173176

174177
joinJtaTransaction();

hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jta/AbstractBasicJtaTestScenarios.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import javax.transaction.SystemException;
1111
import javax.transaction.TransactionManager;
1212

13+
import org.hibernate.resource.transaction.TransactionRequiredForJoinException;
1314
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl;
1415
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl;
1516
import org.hibernate.resource.transaction.backend.jta.internal.synchronization.SynchronizationCallbackCoordinatorTrackingImpl;
@@ -22,6 +23,7 @@
2223
import static org.junit.Assert.assertEquals;
2324
import static org.junit.Assert.assertFalse;
2425
import static org.junit.Assert.assertTrue;
26+
import static org.junit.Assert.fail;
2527

2628
/**
2729
* @author Steve Ebersole
@@ -299,6 +301,26 @@ public void assureMultipleJoinCallsNoOp() throws Exception {
299301
assertEquals( 0, localSync.getFailedCompletionCount() );
300302
}
301303

304+
@Test
305+
@SuppressWarnings("EmptyCatchBlock")
306+
public void explicitJoinOutsideTxnTest() throws Exception {
307+
// pre conditions
308+
final TransactionManager tm = JtaPlatformStandardTestingImpl.INSTANCE.transactionManager();
309+
assertEquals( Status.STATUS_NO_TRANSACTION, tm.getStatus() );
310+
311+
final JtaTransactionCoordinatorImpl transactionCoordinator = buildTransactionCoordinator( false );
312+
313+
assertEquals( Status.STATUS_NO_TRANSACTION, tm.getStatus() );
314+
315+
// try to force a join, should fail...
316+
try {
317+
transactionCoordinator.explicitJoin();
318+
fail( "Expecting explicitJoin() call outside of transaction to fail" );
319+
}
320+
catch (TransactionRequiredForJoinException expected) {
321+
}
322+
}
323+
302324
@Test
303325
public void basicThreadCheckingUsage() throws Exception {
304326
JtaTransactionCoordinatorImpl transactionCoordinator = new JtaTransactionCoordinatorImpl(

hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerImpl.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
import org.hibernate.HibernateException;
2020
import org.hibernate.Interceptor;
2121
import org.hibernate.Session;
22-
import org.hibernate.annotations.common.util.ReflectHelper;
22+
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
23+
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
2324
import org.hibernate.ejb.AbstractEntityManagerImpl;
2425
import org.hibernate.engine.spi.SessionBuilderImplementor;
2526
import org.hibernate.engine.spi.SessionImplementor;
@@ -66,11 +67,11 @@ public EntityManagerImpl(
6667
sessionInterceptorClass = (Class) localSessionInterceptor;
6768
}
6869
else if (localSessionInterceptor instanceof String) {
70+
final ClassLoaderService cls = entityManagerFactory.getSessionFactory().getServiceRegistry().getService( ClassLoaderService.class );
6971
try {
70-
sessionInterceptorClass =
71-
ReflectHelper.classForName( (String) localSessionInterceptor, EntityManagerImpl.class );
72+
sessionInterceptorClass = cls.classForName( (String) localSessionInterceptor );
7273
}
73-
catch (ClassNotFoundException e) {
74+
catch (ClassLoadingException e) {
7475
throw new PersistenceException("Unable to instanciate interceptor: " + localSessionInterceptor, e);
7576
}
7677
}
@@ -128,7 +129,7 @@ protected Session internalGetSession() {
128129
throw new PersistenceException("Session interceptor does not implement Interceptor: " + sessionInterceptorClass, e);
129130
}
130131
}
131-
sessionBuilder.autoJoinTransactions( getTransactionType() != PersistenceUnitTransactionType.JTA );
132+
sessionBuilder.autoJoinTransactions( getSynchronizationType() == SynchronizationType.SYNCHRONIZED );
132133
session = sessionBuilder.openSession();
133134
}
134135
return session;
@@ -250,7 +251,7 @@ public void doAction( boolean successful) {
250251
}
251252

252253
if ( !successful && EntityManagerImpl.this.getTransactionType() == PersistenceUnitTransactionType.JTA ) {
253-
((Session) session).clear();
254+
session.clear();
254255
}
255256
}
256257
}

hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/AbstractEntityManagerImpl.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
import org.hibernate.procedure.ProcedureCallMemento;
104104
import org.hibernate.procedure.UnknownSqlResultSetMappingException;
105105
import org.hibernate.proxy.HibernateProxy;
106-
import org.hibernate.resource.transaction.TransactionCoordinator;
106+
import org.hibernate.resource.transaction.TransactionRequiredForJoinException;
107107
import org.hibernate.transform.BasicTransformerAdapter;
108108
import org.hibernate.type.Type;
109109

@@ -165,12 +165,13 @@ public PersistenceUnitTransactionType getTransactionType() {
165165
return transactionType;
166166
}
167167

168+
public SynchronizationType getSynchronizationType() {
169+
return synchronizationType;
170+
}
171+
168172
protected void postInit() {
169-
//register in Sync if needed
170-
if ( transactionType == PersistenceUnitTransactionType.JTA
171-
&& synchronizationType == SynchronizationType.SYNCHRONIZED ) {
172-
joinTransaction( false );
173-
}
173+
// NOTE : pulse() already handles auto-join-ability correctly
174+
( (SessionImplementor) internalGetSession() ).getTransactionCoordinator().pulse();
174175

175176
setDefaultProperties();
176177
applyProperties();
@@ -742,7 +743,9 @@ private QueryImpl buildQueryFromName(String name, Class resultType) {
742743
return createNamedJpqlQuery( jpqlDefinition, resultType );
743744
}
744745

745-
final NamedSQLQueryDefinition nativeQueryDefinition = sfi.getNamedQueryRepository().getNamedSQLQueryDefinition( name );
746+
final NamedSQLQueryDefinition nativeQueryDefinition = sfi.getNamedQueryRepository().getNamedSQLQueryDefinition(
747+
name
748+
);
746749
if ( nativeQueryDefinition != null ) {
747750
return createNamedSqlQuery( nativeQueryDefinition, resultType );
748751
}
@@ -1488,7 +1491,7 @@ public <T> T unwrap(Class<T> clazz) {
14881491

14891492
@Override
14901493
public void markForRollbackOnly() {
1491-
LOG.debugf("Mark transaction for rollback");
1494+
LOG.debugf( "Mark transaction for rollback" );
14921495
if ( tx.isActive() ) {
14931496
tx.setRollbackOnly();
14941497
}
@@ -1536,8 +1539,10 @@ private void joinTransaction(boolean explicitRequest) {
15361539
}
15371540

15381541
try {
1539-
final TransactionCoordinator transactionCoordinator = ((SessionImplementor) internalGetSession()).getTransactionCoordinator();
1540-
transactionCoordinator.explicitJoin();
1542+
( (SessionImplementor) internalGetSession() ).getTransactionCoordinator().explicitJoin();
1543+
}
1544+
catch (TransactionRequiredForJoinException e) {
1545+
throw new TransactionRequiredException( e.getMessage() );
15411546
}
15421547
catch (HibernateException he) {
15431548
throw convert( he );

hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/transaction/TransactionJoinHandlingChecker.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88

99
import javax.persistence.EntityManager;
1010

11-
import org.hibernate.Session;
12-
import org.hibernate.Transaction;
1311
import org.hibernate.engine.spi.SessionImplementor;
1412
import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper;
1513
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl;
@@ -50,8 +48,8 @@ static void validateExplicitJoiningHandling(EntityManager entityManager) throws
5048
session.getFlushMode();
5149
assertTrue( JtaStatusHelper.isActive( TestingJtaPlatformImpl.INSTANCE.getTransactionManager() ) );
5250
assertTrue( transactionCoordinator.isActive() );
53-
assertFalse( transactionCoordinator.isSynchronizationRegistered() );
5451
assertFalse( transactionCoordinator.isJoined() );
52+
assertFalse( transactionCoordinator.isSynchronizationRegistered() );
5553

5654
entityManager.joinTransaction();
5755
assertTrue( JtaStatusHelper.isActive( TestingJtaPlatformImpl.INSTANCE.getTransactionManager() ) );

hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/transaction/TransactionJoiningTest.java

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
*/
77
package org.hibernate.jpa.test.transaction;
88

9+
import java.util.Map;
10+
import java.util.concurrent.CountDownLatch;
911
import javax.persistence.EntityManager;
1012
import javax.persistence.PersistenceException;
13+
import javax.persistence.SynchronizationType;
1114
import javax.persistence.TransactionRequiredException;
1215
import javax.transaction.Status;
13-
import java.util.Map;
14-
import java.util.concurrent.CountDownLatch;
1516

1617
import org.hibernate.HibernateException;
1718
import org.hibernate.engine.spi.SessionImplementor;
@@ -21,12 +22,11 @@
2122
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
2223
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl;
2324

24-
import org.junit.Test;
25-
2625
import org.hibernate.testing.TestForIssue;
2726
import org.hibernate.testing.jta.TestingJtaBootstrap;
2827
import org.hibernate.testing.jta.TestingJtaPlatformImpl;
2928
import org.hibernate.testing.junit4.ExtraAssertions;
29+
import org.junit.Test;
3030

3131
import static org.junit.Assert.assertFalse;
3232
import static org.junit.Assert.assertTrue;
@@ -49,10 +49,27 @@ protected void addConfigOptions(Map options) {
4949
public void testExplicitJoining() throws Exception {
5050
assertFalse( JtaStatusHelper.isActive( TestingJtaPlatformImpl.INSTANCE.getTransactionManager() ) );
5151

52-
EntityManager entityManager = entityManagerFactory().createEntityManager();
52+
EntityManager entityManager = entityManagerFactory().createEntityManager( SynchronizationType.UNSYNCHRONIZED );
5353
TransactionJoinHandlingChecker.validateExplicitJoiningHandling( entityManager );
5454
}
5555

56+
@Test
57+
@SuppressWarnings("EmptyCatchBlock")
58+
public void testExplicitJoiningTransactionRequiredException() throws Exception {
59+
// explicitly calling EntityManager#joinTransaction outside of an active transaction should cause
60+
// a TransactionRequiredException to be thrown
61+
62+
EntityManager entityManager = entityManagerFactory().createEntityManager();
63+
assertFalse("setup problem", JtaStatusHelper.isActive(TestingJtaPlatformImpl.INSTANCE.getTransactionManager()));
64+
65+
try {
66+
entityManager.joinTransaction();
67+
fail( "Expected joinTransaction() to fail since there is no active JTA transaction" );
68+
}
69+
catch (TransactionRequiredException expected) {
70+
}
71+
}
72+
5673
@Test
5774
public void testImplicitJoining() throws Exception {
5875
// here the transaction is started before the EM is opened...
@@ -180,21 +197,6 @@ public void run() {
180197
em.close();
181198
}
182199

183-
@Test
184-
public void testTransactionRequiredException() throws Exception {
185-
186-
assertFalse("setup problem", JtaStatusHelper.isActive(TestingJtaPlatformImpl.INSTANCE.getTransactionManager()));
187-
188-
EntityManager entityManager = entityManagerFactory().createEntityManager();
189-
try {
190-
entityManager.joinTransaction();
191-
fail( "Expected joinTransaction() to fail since there is no active JTA transaction" );
192-
}
193-
catch (TransactionRequiredException expected) {
194-
195-
}
196-
}
197-
198200
@Override
199201
public Class[] getAnnotatedClasses() {
200202
return new Class[] {

0 commit comments

Comments
 (0)