diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java index 4e4191d023..96aa118c04 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java @@ -142,10 +142,19 @@ public class JdbcResourceLocalTransactionCoordinatorImpl implements TransactionC private void beforeCompletionCallback() { log.trace( "ResourceLocalTransactionCoordinatorImpl#beforeCompletionCallback" ); - transactionCoordinatorOwner.beforeTransactionCompletion(); - synchronizationRegistry.notifySynchronizationsBeforeTransactionCompletion(); - for ( TransactionObserver observer : observers ) { - observer.beforeCompletion(); + try { + transactionCoordinatorOwner.beforeTransactionCompletion(); + synchronizationRegistry.notifySynchronizationsBeforeTransactionCompletion(); + for ( TransactionObserver observer : observers ) { + observer.beforeCompletion(); + } + } + catch (RuntimeException e) { + if ( physicalTransactionDelegate != null ) { + // should never happen that the physicalTransactionDelegate is null, but to be safe + physicalTransactionDelegate.markRollbackOnly(); + } + throw e; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/resource/common/SynchronizationErrorImpl.java b/hibernate-core/src/test/java/org/hibernate/test/resource/common/SynchronizationErrorImpl.java index 5ce3327f11..63c43e4156 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/resource/common/SynchronizationErrorImpl.java +++ b/hibernate-core/src/test/java/org/hibernate/test/resource/common/SynchronizationErrorImpl.java @@ -15,7 +15,19 @@ public class SynchronizationErrorImpl implements Synchronization { private final boolean errorOnBefore; private final boolean errorOnAfter; - public SynchronizationErrorImpl(boolean errorOnBefore, boolean errorOnAfter) { + public static SynchronizationErrorImpl forBefore() { + return new SynchronizationErrorImpl( true, false ); + } + + public static SynchronizationErrorImpl forAfter() { + return new SynchronizationErrorImpl( false, true ); + } + + public static SynchronizationErrorImpl forBoth() { + return new SynchronizationErrorImpl( true, true ); + } + + private SynchronizationErrorImpl(boolean errorOnBefore, boolean errorOnAfter) { this.errorOnBefore = errorOnBefore; this.errorOnAfter = errorOnAfter; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/SynchronizationRegistryStandardImplTests.java b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/SynchronizationRegistryStandardImplTests.java index 1661bd92c1..73efdb1360 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/SynchronizationRegistryStandardImplTests.java +++ b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/SynchronizationRegistryStandardImplTests.java @@ -89,7 +89,7 @@ public class SynchronizationRegistryStandardImplTests { public void testUserSynchronizationExceptions() { // exception in beforeCompletion SynchronizationRegistryStandardImpl registry = new SynchronizationRegistryStandardImpl(); - Synchronization synchronization = new SynchronizationErrorImpl( true, false ); + Synchronization synchronization = SynchronizationErrorImpl.forBefore(); registry.registerSynchronization( synchronization ); try { registry.notifySynchronizationsBeforeTransactionCompletion(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/BasicJdbcTransactionTests.java b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/BasicJdbcTransactionTests.java index 316bc06a75..dfb20b0e81 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/BasicJdbcTransactionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/BasicJdbcTransactionTests.java @@ -6,6 +6,9 @@ */ package org.hibernate.test.resource.transaction.jdbc; +import javax.transaction.Synchronization; + +import org.hibernate.HibernateException; import org.hibernate.TransactionException; import org.hibernate.resource.transaction.TransactionCoordinator; import org.hibernate.resource.transaction.TransactionCoordinatorBuilder; @@ -13,6 +16,7 @@ import org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLoca import org.hibernate.resource.transaction.spi.TransactionStatus; import org.hibernate.test.resource.common.SynchronizationCollectorImpl; +import org.hibernate.test.resource.common.SynchronizationErrorImpl; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -88,4 +92,38 @@ public class BasicJdbcTransactionTests { transactionCoordinator.getTransactionDriverControl().rollback(); } } + + @Test + @SuppressWarnings("EmptyCatchBlock") + public void testSynchronizationFailureMarksTransactionForRollbackOnly() { + final TransactionCoordinatorOwnerTestingImpl owner = new TransactionCoordinatorOwnerTestingImpl(); + final JdbcResourceLocalTransactionCoordinatorBuilderImpl transactionCoordinatorBuilder = + new JdbcResourceLocalTransactionCoordinatorBuilderImpl(); + + final TransactionCoordinator transactionCoordinator = transactionCoordinatorBuilder.buildTransactionCoordinator( + owner, + new TransactionCoordinatorBuilder.TransactionCoordinatorOptions() { + @Override + public boolean shouldAutoJoinTransaction() { + return false; + } + } + ); + + assertEquals( TransactionStatus.NOT_ACTIVE, transactionCoordinator.getTransactionDriverControl().getStatus() ); + transactionCoordinator.getLocalSynchronizations().registerSynchronization( SynchronizationErrorImpl.forBefore() ); + + transactionCoordinator.getTransactionDriverControl().begin(); + assertEquals( TransactionStatus.ACTIVE, transactionCoordinator.getTransactionDriverControl().getStatus() ); + + try { + transactionCoordinator.getTransactionDriverControl().commit(); + } + catch (Exception expected) { + } + finally { + assertEquals( TransactionStatus.MARKED_ROLLBACK, transactionCoordinator.getTransactionDriverControl().getStatus() ); + transactionCoordinator.getTransactionDriverControl().rollback(); + } + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jta/AbstractBasicJtaTestScenarios.java b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jta/AbstractBasicJtaTestScenarios.java index 8823b43e66..5b1a73ba11 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jta/AbstractBasicJtaTestScenarios.java +++ b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jta/AbstractBasicJtaTestScenarios.java @@ -7,15 +7,23 @@ package org.hibernate.test.resource.transaction.jta; import javax.transaction.Status; +import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.TransactionManager; +import org.hibernate.HibernateException; +import org.hibernate.TransactionException; +import org.hibernate.resource.transaction.TransactionCoordinator; +import org.hibernate.resource.transaction.TransactionCoordinatorBuilder; import org.hibernate.resource.transaction.TransactionRequiredForJoinException; +import org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl; 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.synchronization.SynchronizationCallbackCoordinatorTrackingImpl; +import org.hibernate.resource.transaction.spi.TransactionStatus; import org.hibernate.test.resource.common.SynchronizationCollectorImpl; +import org.hibernate.test.resource.common.SynchronizationErrorImpl; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -359,4 +367,75 @@ public abstract class AbstractBasicJtaTestScenarios { tm.rollback(); } + + @Test + @SuppressWarnings("EmptyCatchBlock") + public void testMarkRollbackOnly() throws Exception { + JtaTransactionCoordinatorImpl transactionCoordinator = new JtaTransactionCoordinatorImpl( + transactionCoordinatorBuilder, + owner, + true, + JtaPlatformStandardTestingImpl.INSTANCE, + preferUserTransactions(), + true + ); + + // pre conditions + final TransactionManager tm = JtaPlatformStandardTestingImpl.INSTANCE.transactionManager(); + assertEquals( Status.STATUS_NO_TRANSACTION, tm.getStatus() ); + + assertEquals( TransactionStatus.NOT_ACTIVE, transactionCoordinator.getTransactionDriverControl().getStatus() ); + + transactionCoordinator.getTransactionDriverControl().begin(); + assertEquals( TransactionStatus.ACTIVE, transactionCoordinator.getTransactionDriverControl().getStatus() ); + + transactionCoordinator.getTransactionDriverControl().markRollbackOnly(); + assertEquals( + TransactionStatus.MARKED_ROLLBACK, + transactionCoordinator.getTransactionDriverControl().getStatus() + ); + + try { + transactionCoordinator.getTransactionDriverControl().commit(); + } + catch (TransactionException expected) { + } + finally { + assertEquals( TransactionStatus.MARKED_ROLLBACK, transactionCoordinator.getTransactionDriverControl().getStatus() ); + transactionCoordinator.getTransactionDriverControl().rollback(); + } + } + + @Test + @SuppressWarnings("EmptyCatchBlock") + public void testSynchronizationFailureMarksTransactionForRollbackOnly() throws Exception { + JtaTransactionCoordinatorImpl transactionCoordinator = new JtaTransactionCoordinatorImpl( + transactionCoordinatorBuilder, + owner, + true, + JtaPlatformStandardTestingImpl.INSTANCE, + preferUserTransactions(), + true + ); + + // pre conditions + final TransactionManager tm = JtaPlatformStandardTestingImpl.INSTANCE.transactionManager(); + assertEquals( Status.STATUS_NO_TRANSACTION, tm.getStatus() ); + + assertEquals( TransactionStatus.NOT_ACTIVE, transactionCoordinator.getTransactionDriverControl().getStatus() ); + transactionCoordinator.getLocalSynchronizations().registerSynchronization( SynchronizationErrorImpl.forBefore() ); + + transactionCoordinator.getTransactionDriverControl().begin(); + assertEquals( TransactionStatus.ACTIVE, transactionCoordinator.getTransactionDriverControl().getStatus() ); + + try { + transactionCoordinator.getTransactionDriverControl().commit(); + } + catch (Exception expected) { + } + finally { + assertEquals( TransactionStatus.MARKED_ROLLBACK, transactionCoordinator.getTransactionDriverControl().getStatus() ); + transactionCoordinator.getTransactionDriverControl().rollback(); + } + } }