Skip to content

Commit

Permalink
HHH-9927 - Explicit calls to EntityManager.joinTransaction() with no …
Browse files Browse the repository at this point in the history
…active JTA transaction should throw a TransactionRequiredException
  • Loading branch information
sebersole committed Jul 23, 2015
1 parent 289e59a commit f34c69c
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 45 deletions.
@@ -0,0 +1,25 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.resource.transaction;

import org.hibernate.HibernateException;

/**
* Indicates a call to {@link TransactionCoordinator#explicitJoin()} that requires an
* active transaction where there currently is none.
*
* @author Steve Ebersole
*/
public class TransactionRequiredForJoinException extends HibernateException {
public TransactionRequiredForJoinException(String message) {
super( message );
}

public TransactionRequiredForJoinException(String message, Throwable cause) {
super( message, cause );
}
}
Expand Up @@ -6,14 +6,12 @@
*/ */
package org.hibernate.resource.transaction.backend.jta.internal; package org.hibernate.resource.transaction.backend.jta.internal;


import javax.transaction.Status;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;

import javax.transaction.Status;
import org.jboss.logging.Logger; import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;


import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.boot.spi.SessionFactoryOptions;
Expand All @@ -26,6 +24,7 @@
import org.hibernate.resource.transaction.SynchronizationRegistry; import org.hibernate.resource.transaction.SynchronizationRegistry;
import org.hibernate.resource.transaction.TransactionCoordinator; import org.hibernate.resource.transaction.TransactionCoordinator;
import org.hibernate.resource.transaction.TransactionCoordinatorBuilder; import org.hibernate.resource.transaction.TransactionCoordinatorBuilder;
import org.hibernate.resource.transaction.TransactionRequiredForJoinException;
import org.hibernate.resource.transaction.backend.jta.internal.synchronization.RegisteredSynchronization; import org.hibernate.resource.transaction.backend.jta.internal.synchronization.RegisteredSynchronization;
import org.hibernate.resource.transaction.backend.jta.internal.synchronization.SynchronizationCallbackCoordinator; import org.hibernate.resource.transaction.backend.jta.internal.synchronization.SynchronizationCallbackCoordinator;
import org.hibernate.resource.transaction.backend.jta.internal.synchronization.SynchronizationCallbackCoordinatorNonTrackingImpl; import org.hibernate.resource.transaction.backend.jta.internal.synchronization.SynchronizationCallbackCoordinatorNonTrackingImpl;
Expand All @@ -35,6 +34,8 @@
import org.hibernate.resource.transaction.spi.TransactionCoordinatorOwner; import org.hibernate.resource.transaction.spi.TransactionCoordinatorOwner;
import org.hibernate.resource.transaction.spi.TransactionStatus; import org.hibernate.resource.transaction.spi.TransactionStatus;


import org.jboss.logging.Logger;

import static org.hibernate.internal.CoreLogging.logger; import static org.hibernate.internal.CoreLogging.logger;


/** /**
Expand Down Expand Up @@ -168,7 +169,9 @@ public void explicitJoin() {
} }


if ( getTransactionDriverControl().getStatus() != TransactionStatus.ACTIVE ) { if ( getTransactionDriverControl().getStatus() != TransactionStatus.ACTIVE ) {
return; throw new TransactionRequiredForJoinException(
"Explicitly joining a JTA transaction requires a JTA transaction be currently active"
);
} }


joinJtaTransaction(); joinJtaTransaction();
Expand Down
Expand Up @@ -10,6 +10,7 @@
import javax.transaction.SystemException; import javax.transaction.SystemException;
import javax.transaction.TransactionManager; import javax.transaction.TransactionManager;


import org.hibernate.resource.transaction.TransactionRequiredForJoinException;
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl; import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl;
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl; import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl;
import org.hibernate.resource.transaction.backend.jta.internal.synchronization.SynchronizationCallbackCoordinatorTrackingImpl; import org.hibernate.resource.transaction.backend.jta.internal.synchronization.SynchronizationCallbackCoordinatorTrackingImpl;
Expand All @@ -22,6 +23,7 @@
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;


/** /**
* @author Steve Ebersole * @author Steve Ebersole
Expand Down Expand Up @@ -299,6 +301,26 @@ public void assureMultipleJoinCallsNoOp() throws Exception {
assertEquals( 0, localSync.getFailedCompletionCount() ); assertEquals( 0, localSync.getFailedCompletionCount() );
} }


@Test
@SuppressWarnings("EmptyCatchBlock")
public void explicitJoinOutsideTxnTest() throws Exception {
// pre conditions
final TransactionManager tm = JtaPlatformStandardTestingImpl.INSTANCE.transactionManager();
assertEquals( Status.STATUS_NO_TRANSACTION, tm.getStatus() );

final JtaTransactionCoordinatorImpl transactionCoordinator = buildTransactionCoordinator( false );

assertEquals( Status.STATUS_NO_TRANSACTION, tm.getStatus() );

// try to force a join, should fail...
try {
transactionCoordinator.explicitJoin();
fail( "Expecting explicitJoin() call outside of transaction to fail" );
}
catch (TransactionRequiredForJoinException expected) {
}
}

@Test @Test
public void basicThreadCheckingUsage() throws Exception { public void basicThreadCheckingUsage() throws Exception {
JtaTransactionCoordinatorImpl transactionCoordinator = new JtaTransactionCoordinatorImpl( JtaTransactionCoordinatorImpl transactionCoordinator = new JtaTransactionCoordinatorImpl(
Expand Down
Expand Up @@ -19,7 +19,8 @@
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.Interceptor; import org.hibernate.Interceptor;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.annotations.common.util.ReflectHelper; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.ejb.AbstractEntityManagerImpl; import org.hibernate.ejb.AbstractEntityManagerImpl;
import org.hibernate.engine.spi.SessionBuilderImplementor; import org.hibernate.engine.spi.SessionBuilderImplementor;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
Expand Down Expand Up @@ -66,11 +67,11 @@ public EntityManagerImpl(
sessionInterceptorClass = (Class) localSessionInterceptor; sessionInterceptorClass = (Class) localSessionInterceptor;
} }
else if (localSessionInterceptor instanceof String) { else if (localSessionInterceptor instanceof String) {
final ClassLoaderService cls = entityManagerFactory.getSessionFactory().getServiceRegistry().getService( ClassLoaderService.class );
try { try {
sessionInterceptorClass = sessionInterceptorClass = cls.classForName( (String) localSessionInterceptor );
ReflectHelper.classForName( (String) localSessionInterceptor, EntityManagerImpl.class );
} }
catch (ClassNotFoundException e) { catch (ClassLoadingException e) {
throw new PersistenceException("Unable to instanciate interceptor: " + localSessionInterceptor, e); throw new PersistenceException("Unable to instanciate interceptor: " + localSessionInterceptor, e);
} }
} }
Expand Down Expand Up @@ -128,7 +129,7 @@ protected Session internalGetSession() {
throw new PersistenceException("Session interceptor does not implement Interceptor: " + sessionInterceptorClass, e); throw new PersistenceException("Session interceptor does not implement Interceptor: " + sessionInterceptorClass, e);
} }
} }
sessionBuilder.autoJoinTransactions( getTransactionType() != PersistenceUnitTransactionType.JTA ); sessionBuilder.autoJoinTransactions( getSynchronizationType() == SynchronizationType.SYNCHRONIZED );
session = sessionBuilder.openSession(); session = sessionBuilder.openSession();
} }
return session; return session;
Expand Down Expand Up @@ -250,7 +251,7 @@ public void doAction( boolean successful) {
} }


if ( !successful && EntityManagerImpl.this.getTransactionType() == PersistenceUnitTransactionType.JTA ) { if ( !successful && EntityManagerImpl.this.getTransactionType() == PersistenceUnitTransactionType.JTA ) {
((Session) session).clear(); session.clear();
} }
} }
} }
Expand Down
Expand Up @@ -103,7 +103,7 @@
import org.hibernate.procedure.ProcedureCallMemento; import org.hibernate.procedure.ProcedureCallMemento;
import org.hibernate.procedure.UnknownSqlResultSetMappingException; import org.hibernate.procedure.UnknownSqlResultSetMappingException;
import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.HibernateProxy;
import org.hibernate.resource.transaction.TransactionCoordinator; import org.hibernate.resource.transaction.TransactionRequiredForJoinException;
import org.hibernate.transform.BasicTransformerAdapter; import org.hibernate.transform.BasicTransformerAdapter;
import org.hibernate.type.Type; import org.hibernate.type.Type;


Expand Down Expand Up @@ -165,12 +165,13 @@ public PersistenceUnitTransactionType getTransactionType() {
return transactionType; return transactionType;
} }


public SynchronizationType getSynchronizationType() {
return synchronizationType;
}

protected void postInit() { protected void postInit() {
//register in Sync if needed // NOTE : pulse() already handles auto-join-ability correctly
if ( transactionType == PersistenceUnitTransactionType.JTA ( (SessionImplementor) internalGetSession() ).getTransactionCoordinator().pulse();
&& synchronizationType == SynchronizationType.SYNCHRONIZED ) {
joinTransaction( false );
}


setDefaultProperties(); setDefaultProperties();
applyProperties(); applyProperties();
Expand Down Expand Up @@ -742,7 +743,9 @@ private QueryImpl buildQueryFromName(String name, Class resultType) {
return createNamedJpqlQuery( jpqlDefinition, resultType ); return createNamedJpqlQuery( jpqlDefinition, resultType );
} }


final NamedSQLQueryDefinition nativeQueryDefinition = sfi.getNamedQueryRepository().getNamedSQLQueryDefinition( name ); final NamedSQLQueryDefinition nativeQueryDefinition = sfi.getNamedQueryRepository().getNamedSQLQueryDefinition(
name
);
if ( nativeQueryDefinition != null ) { if ( nativeQueryDefinition != null ) {
return createNamedSqlQuery( nativeQueryDefinition, resultType ); return createNamedSqlQuery( nativeQueryDefinition, resultType );
} }
Expand Down Expand Up @@ -1488,7 +1491,7 @@ public <T> T unwrap(Class<T> clazz) {


@Override @Override
public void markForRollbackOnly() { public void markForRollbackOnly() {
LOG.debugf("Mark transaction for rollback"); LOG.debugf( "Mark transaction for rollback" );
if ( tx.isActive() ) { if ( tx.isActive() ) {
tx.setRollbackOnly(); tx.setRollbackOnly();
} }
Expand Down Expand Up @@ -1536,8 +1539,10 @@ private void joinTransaction(boolean explicitRequest) {
} }


try { try {
final TransactionCoordinator transactionCoordinator = ((SessionImplementor) internalGetSession()).getTransactionCoordinator(); ( (SessionImplementor) internalGetSession() ).getTransactionCoordinator().explicitJoin();
transactionCoordinator.explicitJoin(); }
catch (TransactionRequiredForJoinException e) {
throw new TransactionRequiredException( e.getMessage() );
} }
catch (HibernateException he) { catch (HibernateException he) {
throw convert( he ); throw convert( he );
Expand Down
Expand Up @@ -8,8 +8,6 @@


import javax.persistence.EntityManager; import javax.persistence.EntityManager;


import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper; import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper;
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl; import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl;
Expand Down Expand Up @@ -50,8 +48,8 @@ static void validateExplicitJoiningHandling(EntityManager entityManager) throws
session.getFlushMode(); session.getFlushMode();
assertTrue( JtaStatusHelper.isActive( TestingJtaPlatformImpl.INSTANCE.getTransactionManager() ) ); assertTrue( JtaStatusHelper.isActive( TestingJtaPlatformImpl.INSTANCE.getTransactionManager() ) );
assertTrue( transactionCoordinator.isActive() ); assertTrue( transactionCoordinator.isActive() );
assertFalse( transactionCoordinator.isSynchronizationRegistered() );
assertFalse( transactionCoordinator.isJoined() ); assertFalse( transactionCoordinator.isJoined() );
assertFalse( transactionCoordinator.isSynchronizationRegistered() );


entityManager.joinTransaction(); entityManager.joinTransaction();
assertTrue( JtaStatusHelper.isActive( TestingJtaPlatformImpl.INSTANCE.getTransactionManager() ) ); assertTrue( JtaStatusHelper.isActive( TestingJtaPlatformImpl.INSTANCE.getTransactionManager() ) );
Expand Down
Expand Up @@ -6,12 +6,13 @@
*/ */
package org.hibernate.jpa.test.transaction; package org.hibernate.jpa.test.transaction;


import java.util.Map;
import java.util.concurrent.CountDownLatch;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.PersistenceException; import javax.persistence.PersistenceException;
import javax.persistence.SynchronizationType;
import javax.persistence.TransactionRequiredException; import javax.persistence.TransactionRequiredException;
import javax.transaction.Status; import javax.transaction.Status;
import java.util.Map;
import java.util.concurrent.CountDownLatch;


import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
Expand All @@ -21,12 +22,11 @@
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl; import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl;


import org.junit.Test;

import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.jta.TestingJtaBootstrap; import org.hibernate.testing.jta.TestingJtaBootstrap;
import org.hibernate.testing.jta.TestingJtaPlatformImpl; import org.hibernate.testing.jta.TestingJtaPlatformImpl;
import org.hibernate.testing.junit4.ExtraAssertions; import org.hibernate.testing.junit4.ExtraAssertions;
import org.junit.Test;


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


EntityManager entityManager = entityManagerFactory().createEntityManager(); EntityManager entityManager = entityManagerFactory().createEntityManager( SynchronizationType.UNSYNCHRONIZED );
TransactionJoinHandlingChecker.validateExplicitJoiningHandling( entityManager ); TransactionJoinHandlingChecker.validateExplicitJoiningHandling( entityManager );
} }


@Test
@SuppressWarnings("EmptyCatchBlock")
public void testExplicitJoiningTransactionRequiredException() throws Exception {
// explicitly calling EntityManager#joinTransaction outside of an active transaction should cause
// a TransactionRequiredException to be thrown

EntityManager entityManager = entityManagerFactory().createEntityManager();
assertFalse("setup problem", JtaStatusHelper.isActive(TestingJtaPlatformImpl.INSTANCE.getTransactionManager()));

try {
entityManager.joinTransaction();
fail( "Expected joinTransaction() to fail since there is no active JTA transaction" );
}
catch (TransactionRequiredException expected) {
}
}

@Test @Test
public void testImplicitJoining() throws Exception { public void testImplicitJoining() throws Exception {
// here the transaction is started before the EM is opened... // here the transaction is started before the EM is opened...
Expand Down Expand Up @@ -180,21 +197,6 @@ public void run() {
em.close(); em.close();
} }


@Test
public void testTransactionRequiredException() throws Exception {

assertFalse("setup problem", JtaStatusHelper.isActive(TestingJtaPlatformImpl.INSTANCE.getTransactionManager()));

EntityManager entityManager = entityManagerFactory().createEntityManager();
try {
entityManager.joinTransaction();
fail( "Expected joinTransaction() to fail since there is no active JTA transaction" );
}
catch (TransactionRequiredException expected) {

}
}

@Override @Override
public Class[] getAnnotatedClasses() { public Class[] getAnnotatedClasses() {
return new Class[] { return new Class[] {
Expand Down

0 comments on commit f34c69c

Please sign in to comment.