From 3253d2de89ebfdb6380e8251f2c0aab62ff65808 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 6 Aug 2023 14:03:44 +0200 Subject: [PATCH] Introduce TransactionExecutionListener with begin/commit/rollback notifications Includes ConfigurableTransactionManager interface for listener registration. Includes additional introspection methods on TransactionExecution interface. Includes default method declarations on TransactionStatus/SmartTransactionObject. Closes gh-27479 --- .../JdbcTransactionObjectSupport.java | 5 - .../DataSourceTransactionManagerTests.java | 594 ++++++++++-------- .../jms/connection/JmsTransactionManager.java | 5 - .../R2dbcTransactionManagerUnitTests.java | 198 +++--- .../ConfigurableTransactionManager.java | 54 ++ .../PlatformTransactionManager.java | 4 +- .../transaction/ReactiveTransaction.java | 2 +- .../ReactiveTransactionManager.java | 1 + .../transaction/TransactionExecution.java | 90 ++- .../TransactionExecutionListener.java | 88 +++ .../transaction/TransactionStatus.java | 14 +- .../AbstractReactiveTransactionManager.java | 107 +++- .../reactive/GenericReactiveTransaction.java | 47 +- .../AbstractPlatformTransactionManager.java | 117 +++- .../support/AbstractTransactionStatus.java | 17 +- .../support/DefaultTransactionStatus.java | 42 +- .../support/SmartTransactionObject.java | 17 +- .../JtaTransactionManagerTests.java | 110 +++- .../TestTransactionExecutionListener.java | 82 +++ .../ReactiveTestTransactionManager.java | 31 +- .../ReactiveTransactionSupportTests.java | 142 ++++- .../support/TransactionSupportTests.java | 86 ++- 22 files changed, 1372 insertions(+), 481 deletions(-) create mode 100644 spring-tx/src/main/java/org/springframework/transaction/ConfigurableTransactionManager.java create mode 100644 spring-tx/src/main/java/org/springframework/transaction/TransactionExecutionListener.java create mode 100644 spring-tx/src/test/java/org/springframework/transaction/TestTransactionExecutionListener.java diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java index 412a67af0d..4c94052bc3 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java @@ -131,11 +131,6 @@ public abstract class JdbcTransactionObjectSupport implements SavepointManager, return this.savepointAllowed; } - @Override - public void flush() { - // no-op - } - //--------------------------------------------------------------------- // Implementation of SavepointManager diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java index dea0043262..2fdc9e0683 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java @@ -136,15 +136,15 @@ public class DataSourceTransactionManagerTests { final DataSource dsToUse = (lazyConnection ? new LazyConnectionDataSourceProxy(ds) : ds); tm = new DataSourceTransactionManager(dsToUse); TransactionTemplate tt = new TransactionTemplate(tm); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); Connection tCon = DataSourceUtils.getConnection(dsToUse); @@ -163,8 +163,8 @@ public class DataSourceTransactionManagerTests { } }); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); if (autoCommit && (!lazyConnection || createStatement)) { InOrder ordered = inOrder(con); @@ -225,17 +225,17 @@ public class DataSourceTransactionManagerTests { final DataSource dsToUse = (lazyConnection ? new LazyConnectionDataSourceProxy(ds) : ds); tm = new DataSourceTransactionManager(dsToUse); TransactionTemplate tt = new TransactionTemplate(tm); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); final RuntimeException ex = new RuntimeException("Application exception"); assertThatRuntimeException().isThrownBy(() -> tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); Connection con = DataSourceUtils.getConnection(dsToUse); if (createStatement) { try { @@ -250,8 +250,8 @@ public class DataSourceTransactionManagerTests { })) .isEqualTo(ex); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); if (autoCommit && (!lazyConnection || createStatement)) { InOrder ordered = inOrder(con); @@ -271,8 +271,8 @@ public class DataSourceTransactionManagerTests { public void testTransactionRollbackOnly() throws Exception { tm.setTransactionSynchronization(DataSourceTransactionManager.SYNCHRONIZATION_NEVER); TransactionTemplate tt = new TransactionTemplate(tm); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); ConnectionHolder conHolder = new ConnectionHolder(con, true); TransactionSynchronizationManager.bindResource(ds, conHolder); @@ -281,9 +281,9 @@ public class DataSourceTransactionManagerTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); + assertThat(status.isNewTransaction()).isFalse(); throw ex; } }); @@ -291,14 +291,14 @@ public class DataSourceTransactionManagerTests { } catch (RuntimeException ex2) { // expected - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); assertThat(ex2).as("Correct exception thrown").isEqualTo(ex); } finally { TransactionSynchronizationManager.unbindResource(ds); } - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); } @Test @@ -316,8 +316,8 @@ public class DataSourceTransactionManagerTests { if (failEarly) { tm.setFailEarlyOnGlobalRollbackOnly(true); } - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition()); TestTransactionSynchronization synch = @@ -326,25 +326,25 @@ public class DataSourceTransactionManagerTests { boolean outerTransactionBoundaryReached = false; try { - assertThat(ts.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(ts.isNewTransaction()).isTrue(); final TransactionTemplate tt = new TransactionTemplate(tm); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); - assertThat(status.isRollbackOnly()).as("Is not rollback-only").isFalse(); + assertThat(status.isNewTransaction()).isFalse(); + assertThat(status.isRollbackOnly()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.isNewTransaction()).isFalse(); status.setRollbackOnly(); } }); - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); - assertThat(status.isRollbackOnly()).as("Is rollback-only").isTrue(); + assertThat(status.isNewTransaction()).isFalse(); + assertThat(status.isRollbackOnly()).isTrue(); } }); @@ -366,7 +366,7 @@ public class DataSourceTransactionManagerTests { } } - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); assertThat(synch.beforeCommitCalled).isFalse(); assertThat(synch.beforeCompletionCalled).isTrue(); assertThat(synch.afterCommitCalled).isFalse(); @@ -379,8 +379,8 @@ public class DataSourceTransactionManagerTests { public void testParticipatingTransactionWithIncompatibleIsolationLevel() throws Exception { tm.setValidateExistingTransaction(true); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); assertThatExceptionOfType(IllegalTransactionStateException.class).isThrownBy(() -> { final TransactionTemplate tt = new TransactionTemplate(tm); @@ -390,19 +390,19 @@ public class DataSourceTransactionManagerTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isRollbackOnly()).as("Is not rollback-only").isFalse(); + assertThat(status.isRollbackOnly()).isFalse(); tt2.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { status.setRollbackOnly(); } }); - assertThat(status.isRollbackOnly()).as("Is rollback-only").isTrue(); + assertThat(status.isRollbackOnly()).isTrue(); } }); }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).rollback(); verify(con).close(); } @@ -412,8 +412,8 @@ public class DataSourceTransactionManagerTests { willThrow(new SQLException("read-only not supported")).given(con).setReadOnly(true); tm.setValidateExistingTransaction(true); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); assertThatExceptionOfType(IllegalTransactionStateException.class).isThrownBy(() -> { final TransactionTemplate tt = new TransactionTemplate(tm); @@ -424,27 +424,27 @@ public class DataSourceTransactionManagerTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isRollbackOnly()).as("Is not rollback-only").isFalse(); + assertThat(status.isRollbackOnly()).isFalse(); tt2.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { status.setRollbackOnly(); } }); - assertThat(status.isRollbackOnly()).as("Is rollback-only").isTrue(); + assertThat(status.isRollbackOnly()).isTrue(); } }); }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).rollback(); verify(con).close(); } @Test public void testParticipatingTransactionWithTransactionStartedFromSynch() throws Exception { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); @@ -470,7 +470,7 @@ public class DataSourceTransactionManagerTests { } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); assertThat(synch.beforeCommitCalled).isTrue(); assertThat(synch.beforeCompletionCalled).isTrue(); assertThat(synch.afterCommitCalled).isTrue(); @@ -486,8 +486,8 @@ public class DataSourceTransactionManagerTests { final Connection con2 = mock(); given(ds2.getConnection()).willReturn(con2); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); final TransactionTemplate tt = new TransactionTemplate(tm); @@ -508,7 +508,7 @@ public class DataSourceTransactionManagerTests { } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); assertThat(synch.beforeCommitCalled).isTrue(); assertThat(synch.beforeCompletionCalled).isTrue(); assertThat(synch.afterCommitCalled).isTrue(); @@ -525,32 +525,32 @@ public class DataSourceTransactionManagerTests { DataSourceTransactionManager tm2 = new DataSourceTransactionManager(ds); // tm has no synch enabled (used at outer level), tm2 has synch enabled (inner level) - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition()); final TestTransactionSynchronization synch = new TestTransactionSynchronization(ds, TransactionSynchronization.STATUS_UNKNOWN); assertThatExceptionOfType(UnexpectedRollbackException.class).isThrownBy(() -> { - assertThat(ts.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(ts.isNewTransaction()).isTrue(); final TransactionTemplate tt = new TransactionTemplate(tm2); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); - assertThat(status.isRollbackOnly()).as("Is not rollback-only").isFalse(); + assertThat(status.isNewTransaction()).isFalse(); + assertThat(status.isRollbackOnly()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.isNewTransaction()).isFalse(); status.setRollbackOnly(); } }); - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); - assertThat(status.isRollbackOnly()).as("Is rollback-only").isTrue(); + assertThat(status.isNewTransaction()).isFalse(); + assertThat(status.isRollbackOnly()).isTrue(); TransactionSynchronizationManager.registerSynchronization(synch); } }); @@ -558,7 +558,7 @@ public class DataSourceTransactionManagerTests { tm.commit(ts); }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); assertThat(synch.beforeCommitCalled).isFalse(); assertThat(synch.beforeCompletionCalled).isTrue(); assertThat(synch.afterCommitCalled).isFalse(); @@ -571,34 +571,34 @@ public class DataSourceTransactionManagerTests { public void testPropagationRequiresNewWithExistingTransaction() throws Exception { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); status.setRollbackOnly(); } }); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(status.isNewTransaction()).isTrue(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).rollback(); verify(con).commit(); verify(con, times(2)).close(); @@ -617,36 +617,36 @@ public class DataSourceTransactionManagerTests { final TransactionTemplate tt2 = new TransactionTemplate(tm2); tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.hasResource(ds2)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds2)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); tt2.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); status.setRollbackOnly(); } }); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(status.isNewTransaction()).isTrue(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.hasResource(ds2)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds2)).isFalse(); verify(con).commit(); verify(con).close(); verify(con2).rollback(); @@ -667,16 +667,19 @@ public class DataSourceTransactionManagerTests { final TransactionTemplate tt2 = new TransactionTemplate(tm2); tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.hasResource(ds2)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds2)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); assertThatExceptionOfType(CannotCreateTransactionException.class).isThrownBy(() -> tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.isReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); tt2.execute(new TransactionCallbackWithoutResult() { @@ -688,8 +691,8 @@ public class DataSourceTransactionManagerTests { } })).withCause(failure); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.hasResource(ds2)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds2)).isFalse(); verify(con).rollback(); verify(con).close(); } @@ -698,34 +701,39 @@ public class DataSourceTransactionManagerTests { public void testPropagationNotSupportedWithExistingTransaction() throws Exception { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.hasTransaction()).isFalse(); + assertThat(status.isNewTransaction()).isFalse(); + assertThat(status.isNested()).isFalse(); + assertThat(status.isReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isFalse(); status.setRollbackOnly(); } }); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(status.isNewTransaction()).isTrue(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).commit(); verify(con).close(); } @@ -734,14 +742,16 @@ public class DataSourceTransactionManagerTests { public void testPropagationNeverWithExistingTransaction() throws Exception { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); assertThatExceptionOfType(IllegalTransactionStateException.class).isThrownBy(() -> tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER); tt.execute(new TransactionCallbackWithoutResult() { @Override @@ -753,7 +763,7 @@ public class DataSourceTransactionManagerTests { } })); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).rollback(); verify(con).close(); } @@ -762,21 +772,26 @@ public class DataSourceTransactionManagerTests { public void testPropagationSupportsAndRequiresNew() throws Exception { TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.hasTransaction()).isFalse(); + assertThat(status.isNewTransaction()).isFalse(); + assertThat(status.isNested()).isFalse(); TransactionTemplate tt2 = new TransactionTemplate(tm); tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); tt2.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con); assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con); } @@ -784,7 +799,7 @@ public class DataSourceTransactionManagerTests { } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).commit(); verify(con).close(); } @@ -798,32 +813,37 @@ public class DataSourceTransactionManagerTests { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con1); assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con1); + assertThat(status.hasTransaction()).isFalse(); + assertThat(status.isNewTransaction()).isFalse(); + assertThat(status.isNested()).isFalse(); TransactionTemplate tt2 = new TransactionTemplate(tm); tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); tt2.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con2); assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con2); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); } }); assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con1); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con1).close(); verify(con2).commit(); verify(con2).close(); @@ -838,17 +858,26 @@ public class DataSourceTransactionManagerTests { tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); tt.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE); tt.setReadOnly(true); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + tt.setName("my-transaction"); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { + // something transactional + assertThat(status.getTransactionName()).isEqualTo("my-transaction"); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); + assertThat(status.isReadOnly()).isTrue(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isTrue(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - // something transactional + assertThat(status.isRollbackOnly()).isFalse(); + assertThat(status.isCompleted()).isFalse(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); InOrder ordered = inOrder(con); ordered.verify(con).setReadOnly(true); ordered.verify(con).setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); @@ -871,17 +900,25 @@ public class DataSourceTransactionManagerTests { TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); tt.setReadOnly(true); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { + // something transactional + assertThat(status.getTransactionName()).isEmpty(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); + assertThat(status.isReadOnly()).isTrue(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isTrue(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - // something transactional + assertThat(status.isRollbackOnly()).isFalse(); + assertThat(status.isCompleted()).isFalse(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); InOrder ordered = inOrder(con, stmt); ordered.verify(con).setReadOnly(true); ordered.verify(con).setAutoCommit(false); @@ -903,7 +940,7 @@ public class DataSourceTransactionManagerTests { TransactionTemplate tt = new TransactionTemplate(tm); tt.setTimeout(timeout); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); try { tt.execute(new TransactionCallbackWithoutResult() { @@ -937,7 +974,7 @@ public class DataSourceTransactionManagerTests { } } - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); if (timeout > 1) { verify(ps).setQueryTimeout(timeout - 1); verify(con).commit(); @@ -957,7 +994,7 @@ public class DataSourceTransactionManagerTests { given(con.getWarnings()).willThrow(new SQLException()); TransactionTemplate tt = new TransactionTemplate(tm); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { @@ -978,7 +1015,7 @@ public class DataSourceTransactionManagerTests { } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); InOrder ordered = inOrder(con); ordered.verify(con).setAutoCommit(false); ordered.verify(con).commit(); @@ -992,7 +1029,7 @@ public class DataSourceTransactionManagerTests { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override @@ -1036,7 +1073,7 @@ public class DataSourceTransactionManagerTests { } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); InOrder ordered = inOrder(con); ordered.verify(con).setAutoCommit(false); ordered.verify(con).commit(); @@ -1050,7 +1087,7 @@ public class DataSourceTransactionManagerTests { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override @@ -1095,7 +1132,7 @@ public class DataSourceTransactionManagerTests { } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); InOrder ordered = inOrder(con); ordered.verify(con).setAutoCommit(false); ordered.verify(con).commit(); @@ -1112,14 +1149,14 @@ public class DataSourceTransactionManagerTests { TransactionTemplate tt = new TransactionTemplate(tm); assertThatExceptionOfType(CannotCreateTransactionException.class).isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - // something transactional - } - })); + tt.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + // something transactional + } + })); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).close(); } @@ -1129,14 +1166,14 @@ public class DataSourceTransactionManagerTests { TransactionTemplate tt = new TransactionTemplate(tm); assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - // something transactional - } - })); + tt.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + // something transactional + } + })); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).close(); } @@ -1147,14 +1184,14 @@ public class DataSourceTransactionManagerTests { tm.setRollbackOnCommitFailure(true); TransactionTemplate tt = new TransactionTemplate(tm); assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - // something transactional - } - })); + tt.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + // something transactional + } + })); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).rollback(); verify(con).close(); } @@ -1166,14 +1203,23 @@ public class DataSourceTransactionManagerTests { TransactionTemplate tt = new TransactionTemplate(tm); assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - status.setRollbackOnly(); - } - })); + tt.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { + assertThat(status.getTransactionName()).isEmpty(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); + assertThat(status.isReadOnly()).isFalse(); + assertThat(status.isRollbackOnly()).isFalse(); + status.setRollbackOnly(); + assertThat(status.isRollbackOnly()).isTrue(); + assertThat(status.isCompleted()).isFalse(); + } + })); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); InOrder ordered = inOrder(con); ordered.verify(con).setAutoCommit(false); ordered.verify(con).rollback(); @@ -1185,53 +1231,53 @@ public class DataSourceTransactionManagerTests { public void testTransactionWithPropagationSupports() throws Exception { TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(status.isNewTransaction()).as("Is not new transaction").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(status.isNewTransaction()).isFalse(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isFalse(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); } @Test public void testTransactionWithPropagationNotSupported() throws Exception { TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(status.isNewTransaction()).as("Is not new transaction").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(status.isNewTransaction()).isFalse(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); } @Test public void testTransactionWithPropagationNever() throws Exception { TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(status.isNewTransaction()).as("Is not new transaction").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(status.isNewTransaction()).isFalse(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); } @Test @@ -1256,32 +1302,37 @@ public class DataSourceTransactionManagerTests { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active") - .isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); for (int i = 0; i < count; i++) { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); - assertThat(status.hasSavepoint()).as("Is nested transaction").isTrue(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isFalse(); + assertThat(status.isNested()).isTrue(); + assertThat(status.hasSavepoint()).isTrue(); } }); } - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con, times(count)).releaseSavepoint(sp); verify(con).commit(); verify(con).close(); @@ -1298,31 +1349,36 @@ public class DataSourceTransactionManagerTests { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active") - .isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); - assertThat(status.hasSavepoint()).as("Is nested transaction").isTrue(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isFalse(); + assertThat(status.isNested()).isTrue(); + assertThat(status.hasSavepoint()).isTrue(); status.setRollbackOnly(); } }); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).rollback(sp); verify(con).releaseSavepoint(sp); verify(con).commit(); @@ -1340,42 +1396,49 @@ public class DataSourceTransactionManagerTests { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active") - .isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); assertThatIllegalStateException().isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); - assertThat(status.hasSavepoint()).as("Is nested transaction").isTrue(); - TransactionTemplate ntt = new TransactionTemplate(tm); - ntt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); - assertThat(status.hasSavepoint()).as("Is regular transaction").isFalse(); - throw new IllegalStateException(); - } - }); - } - })); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); + tt.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { + assertThat(TransactionSynchronizationManager.hasResource(ds)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isFalse(); + assertThat(status.isNested()).isTrue(); + assertThat(status.hasSavepoint()).isTrue(); + TransactionTemplate ntt = new TransactionTemplate(tm); + ntt.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { + assertThat(TransactionSynchronizationManager.hasResource(ds)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isFalse(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); + throw new IllegalStateException(); + } + }); + } + })); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).rollback(sp); verify(con).releaseSavepoint(sp); verify(con).commit(); @@ -1393,42 +1456,49 @@ public class DataSourceTransactionManagerTests { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active") - .isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); assertThatExceptionOfType(UnexpectedRollbackException.class).isThrownBy(() -> tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); - assertThat(status.hasSavepoint()).as("Is nested transaction").isTrue(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isFalse(); + assertThat(status.isNested()).isTrue(); + assertThat(status.hasSavepoint()).isTrue(); TransactionTemplate ntt = new TransactionTemplate(tm); ntt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); - assertThat(status.hasSavepoint()).as("Is regular transaction").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isFalse(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); status.setRollbackOnly(); } }); } })); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).rollback(sp); verify(con).releaseSavepoint(sp); verify(con).commit(); @@ -1446,21 +1516,22 @@ public class DataSourceTransactionManagerTests { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active") - .isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); Object savepoint = status.createSavepoint(); status.releaseSavepoint(savepoint); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).releaseSavepoint(sp); verify(con).commit(); verify(con).close(); @@ -1478,20 +1549,22 @@ public class DataSourceTransactionManagerTests { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); Object savepoint = status.createSavepoint(); status.rollbackToSavepoint(savepoint); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).rollback(sp); verify(con).commit(); verify(con).close(); @@ -1501,17 +1574,24 @@ public class DataSourceTransactionManagerTests { public void testTransactionWithPropagationNested() throws Exception { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(status.getTransactionName()).isEmpty(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); + assertThat(status.isReadOnly()).isFalse(); + assertThat(status.isRollbackOnly()).isFalse(); + assertThat(status.isCompleted()).isFalse(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).commit(); verify(con).close(); } @@ -1520,18 +1600,26 @@ public class DataSourceTransactionManagerTests { public void testTransactionWithPropagationNestedAndRollback() throws Exception { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(status.getTransactionName()).isEmpty(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); + assertThat(status.isReadOnly()).isFalse(); + assertThat(status.isRollbackOnly()).isFalse(); status.setRollbackOnly(); + assertThat(status.isRollbackOnly()).isTrue(); + assertThat(status.isCompleted()).isFalse(); } }); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).rollback(); verify(con).close(); } diff --git a/spring-jms/src/main/java/org/springframework/jms/connection/JmsTransactionManager.java b/spring-jms/src/main/java/org/springframework/jms/connection/JmsTransactionManager.java index 6d356ef147..3ffd27e867 100644 --- a/spring-jms/src/main/java/org/springframework/jms/connection/JmsTransactionManager.java +++ b/spring-jms/src/main/java/org/springframework/jms/connection/JmsTransactionManager.java @@ -448,11 +448,6 @@ public class JmsTransactionManager extends AbstractPlatformTransactionManager public boolean isRollbackOnly() { return (this.resourceHolder != null && this.resourceHolder.isRollbackOnly()); } - - @Override - public void flush() { - // no-op - } } } diff --git a/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/R2dbcTransactionManagerUnitTests.java b/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/R2dbcTransactionManagerUnitTests.java index 89ccc9fe2d..99257abecb 100644 --- a/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/R2dbcTransactionManagerUnitTests.java +++ b/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/R2dbcTransactionManagerUnitTests.java @@ -88,8 +88,7 @@ class R2dbcTransactionManagerUnitTests { ConnectionFactoryUtils.getConnection(connectionFactoryMock) .flatMap(connection -> TransactionSynchronizationManager.forCurrentTransaction() - .doOnNext(synchronizationManager -> synchronizationManager.registerSynchronization( - sync))) + .doOnNext(synchronizationManager -> synchronizationManager.registerSynchronization(sync))) .as(operator::transactional) .as(StepVerifier::create) .expectNextCount(1) @@ -120,12 +119,11 @@ class R2dbcTransactionManagerUnitTests { TransactionalOperator operator = TransactionalOperator.create(tm, definition); - ConnectionFactoryUtils.getConnection(connectionFactoryMock).as( - operator::transactional) + ConnectionFactoryUtils.getConnection(connectionFactoryMock) + .as(operator::transactional) .as(StepVerifier::create) .expectErrorSatisfies(actual -> assertThat(actual).isInstanceOf( - CannotCreateTransactionException.class).hasCauseInstanceOf( - R2dbcBadGrammarException.class)) + CannotCreateTransactionException.class).hasCauseInstanceOf(R2dbcBadGrammarException.class)) .verify(); } @@ -140,12 +138,16 @@ class R2dbcTransactionManagerUnitTests { definition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE); TransactionalOperator operator = TransactionalOperator.create(tm, definition); - - ConnectionFactoryUtils.getConnection(connectionFactoryMock).as( - operator::transactional) - .as(StepVerifier::create) - .expectNextCount(1) - .verifyComplete(); + operator.execute(tx -> { + assertThat(tx.getTransactionName()).isEqualTo("my-transaction"); + assertThat(tx.hasTransaction()).isTrue(); + assertThat(tx.isNewTransaction()).isTrue(); + assertThat(tx.isNested()).isFalse(); + assertThat(tx.isReadOnly()).isTrue(); + assertThat(tx.isRollbackOnly()).isFalse(); + assertThat(tx.isCompleted()).isFalse(); + return Mono.empty(); + }).as(StepVerifier::create).verifyComplete(); ArgumentCaptor txCaptor = ArgumentCaptor.forClass(io.r2dbc.spi.TransactionDefinition.class); verify(connectionMock).beginTransaction(txCaptor.capture()); @@ -171,8 +173,8 @@ class R2dbcTransactionManagerUnitTests { TransactionalOperator operator = TransactionalOperator.create(tm, definition); - ConnectionFactoryUtils.getConnection(connectionFactoryMock).as( - operator::transactional) + ConnectionFactoryUtils.getConnection(connectionFactoryMock) + .as(operator::transactional) .as(StepVerifier::create) .expectNextCount(1) .verifyComplete(); @@ -190,8 +192,8 @@ class R2dbcTransactionManagerUnitTests { TransactionalOperator operator = TransactionalOperator.create(tm, definition); - ConnectionFactoryUtils.getConnection(connectionFactoryMock).as( - operator::transactional) + ConnectionFactoryUtils.getConnection(connectionFactoryMock) + .as(operator::transactional) .as(StepVerifier::create) .expectNextCount(1) .verifyComplete(); @@ -215,8 +217,8 @@ class R2dbcTransactionManagerUnitTests { TransactionalOperator operator = TransactionalOperator.create(tm, definition); - ConnectionFactoryUtils.getConnection(connectionFactoryMock).as( - operator::transactional) + ConnectionFactoryUtils.getConnection(connectionFactoryMock) + .as(operator::transactional) .as(StepVerifier::create) .expectNextCount(1) .verifyComplete(); @@ -262,11 +264,9 @@ class R2dbcTransactionManagerUnitTests { TransactionalOperator operator = TransactionalOperator.create(tm); ConnectionFactoryUtils.getConnection(connectionFactoryMock) - .doOnNext(connection -> { - throw new IllegalStateException(); - }).as(operator::transactional) - .as(StepVerifier::create) - .verifyError(IllegalStateException.class); + .doOnNext(connection -> { throw new IllegalStateException(); }) + .as(operator::transactional) + .as(StepVerifier::create).verifyError(IllegalStateException.class); assertThat(commits).hasValue(0); assertThat(rollbacks).hasValue(1); @@ -286,8 +286,7 @@ class R2dbcTransactionManagerUnitTests { reactiveTransaction.setRollbackOnly(); return ConnectionFactoryUtils.getConnection(connectionFactoryMock) .doOnNext(connection -> connection.createStatement("foo")).then(); - }).as(StepVerifier::create) - .verifyError(BadSqlGrammarException.class); + }).as(StepVerifier::create).verifyError(BadSqlGrammarException.class); verify(connectionMock).beginTransaction(any(io.r2dbc.spi.TransactionDefinition.class)); verify(connectionMock).createStatement("foo"); @@ -308,7 +307,7 @@ class R2dbcTransactionManagerUnitTests { .doOnNext(connection -> { throw new IllegalStateException("Intentional error to trigger rollback"); }).then()).as(StepVerifier::create) - .verifyErrorSatisfies(e -> assertThat(e) + .verifyErrorSatisfies(ex -> assertThat(ex) .isInstanceOf(BadSqlGrammarException.class) .hasCause(new R2dbcBadGrammarException("Rollback should fail")) ); @@ -327,15 +326,21 @@ class R2dbcTransactionManagerUnitTests { TransactionalOperator operator = TransactionalOperator.create(tm); operator.execute(tx -> { - tx.setRollbackOnly(); + assertThat(tx.getTransactionName()).isEmpty(); + assertThat(tx.hasTransaction()).isTrue(); assertThat(tx.isNewTransaction()).isTrue(); + assertThat(tx.isNested()).isFalse(); + assertThat(tx.isReadOnly()).isFalse(); + assertThat(tx.isRollbackOnly()).isFalse(); + tx.setRollbackOnly(); + assertThat(tx.isRollbackOnly()).isTrue(); + assertThat(tx.isCompleted()).isFalse(); return TransactionSynchronizationManager.forCurrentTransaction().doOnNext( synchronizationManager -> { assertThat(synchronizationManager.hasResource(connectionFactoryMock)).isTrue(); synchronizationManager.registerSynchronization(sync); }).then(); - }).as(StepVerifier::create) - .verifyComplete(); + }).as(StepVerifier::create).verifyComplete(); verify(connectionMock).beginTransaction(any(io.r2dbc.spi.TransactionDefinition.class)); verify(connectionMock).rollbackTransaction(); @@ -357,14 +362,19 @@ class R2dbcTransactionManagerUnitTests { TransactionalOperator operator = TransactionalOperator.create(tm, definition); operator.execute(tx1 -> { + assertThat(tx1.getTransactionName()).isEmpty(); + assertThat(tx1.hasTransaction()).isTrue(); assertThat(tx1.isNewTransaction()).isTrue(); + assertThat(tx1.isNested()).isFalse(); + assertThat(tx1.isReadOnly()).isFalse(); + assertThat(tx1.isRollbackOnly()).isFalse(); + assertThat(tx1.isCompleted()).isFalse(); definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER); return operator.execute(tx2 -> { fail("Should have thrown IllegalTransactionStateException"); return Mono.empty(); }); - }).as(StepVerifier::create) - .verifyError(IllegalTransactionStateException.class); + }).as(StepVerifier::create).verifyError(IllegalTransactionStateException.class); verify(connectionMock).rollbackTransaction(); verify(connectionMock).close(); @@ -381,14 +391,17 @@ class R2dbcTransactionManagerUnitTests { TransactionalOperator operator = TransactionalOperator.create(tm, definition); operator.execute(tx1 -> { - assertThat(tx1.isNewTransaction()).isTrue(); - definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - return operator.execute(tx2 -> { - assertThat(tx2.isNewTransaction()).isTrue(); - return Mono.empty(); - }); - }).as(StepVerifier::create) - .verifyComplete(); + assertThat(tx1.hasTransaction()).isTrue(); + assertThat(tx1.isNewTransaction()).isTrue(); + assertThat(tx1.isNested()).isFalse(); + definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); + return operator.execute(tx2 -> { + assertThat(tx2.hasTransaction()).isTrue(); + assertThat(tx2.isNewTransaction()).isTrue(); + assertThat(tx2.isNested()).isTrue(); + return Mono.empty(); + }); + }).as(StepVerifier::create).verifyComplete(); verify(connectionMock).createSavepoint("SAVEPOINT_1"); verify(connectionMock).releaseSavepoint("SAVEPOINT_1"); @@ -407,15 +420,20 @@ class R2dbcTransactionManagerUnitTests { TransactionalOperator operator = TransactionalOperator.create(tm, definition); operator.execute(tx1 -> { - assertThat(tx1.isNewTransaction()).isTrue(); - definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - return operator.execute(tx2 -> { - assertThat(tx2.isNewTransaction()).isTrue(); - tx2.setRollbackOnly(); - return Mono.empty(); - }); - }).as(StepVerifier::create) - .verifyComplete(); + assertThat(tx1.hasTransaction()).isTrue(); + assertThat(tx1.isNewTransaction()).isTrue(); + assertThat(tx1.isNested()).isFalse(); + definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); + return operator.execute(tx2 -> { + assertThat(tx2.hasTransaction()).isTrue(); + assertThat(tx2.isNewTransaction()).isTrue(); + assertThat(tx2.isNested()).isTrue(); + assertThat(tx2.isRollbackOnly()).isFalse(); + tx2.setRollbackOnly(); + assertThat(tx2.isRollbackOnly()).isTrue(); + return Mono.empty(); + }); + }).as(StepVerifier::create).verifyComplete(); verify(connectionMock).createSavepoint("SAVEPOINT_1"); verify(connectionMock).rollbackTransactionToSavepoint("SAVEPOINT_1"); @@ -432,16 +450,19 @@ class R2dbcTransactionManagerUnitTests { TransactionalOperator operator = TransactionalOperator.create(tm, definition); operator.execute(tx1 -> { - assertThat(tx1.isNewTransaction()).isFalse(); - DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition(); - innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - TransactionalOperator inner = TransactionalOperator.create(tm, innerDef); - return inner.execute(tx2 -> { - assertThat(tx2.isNewTransaction()).isTrue(); - return Mono.empty(); - }); - }).as(StepVerifier::create) - .verifyComplete(); + assertThat(tx1.hasTransaction()).isFalse(); + assertThat(tx1.isNewTransaction()).isFalse(); + assertThat(tx1.isNested()).isFalse(); + DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition(); + innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); + TransactionalOperator inner = TransactionalOperator.create(tm, innerDef); + return inner.execute(tx2 -> { + assertThat(tx2.hasTransaction()).isTrue(); + assertThat(tx2.isNewTransaction()).isTrue(); + assertThat(tx2.isNested()).isFalse(); + return Mono.empty(); + }); + }).as(StepVerifier::create).verifyComplete(); verify(connectionMock).commitTransaction(); verify(connectionMock).close(); @@ -456,17 +477,22 @@ class R2dbcTransactionManagerUnitTests { TransactionalOperator operator = TransactionalOperator.create(tm, definition); operator.execute(tx1 -> { - assertThat(tx1.isNewTransaction()).isFalse(); - DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition(); - innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - TransactionalOperator inner = TransactionalOperator.create(tm, innerDef); - return inner.execute(tx2 -> { - assertThat(tx2.isNewTransaction()).isTrue(); - tx2.setRollbackOnly(); - return Mono.empty(); - }); - }).as(StepVerifier::create) - .verifyComplete(); + assertThat(tx1.hasTransaction()).isFalse(); + assertThat(tx1.isNewTransaction()).isFalse(); + assertThat(tx1.isNested()).isFalse(); + DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition(); + innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); + TransactionalOperator inner = TransactionalOperator.create(tm, innerDef); + return inner.execute(tx2 -> { + assertThat(tx2.hasTransaction()).isTrue(); + assertThat(tx2.isNewTransaction()).isTrue(); + assertThat(tx2.isNested()).isFalse(); + assertThat(tx2.isRollbackOnly()).isFalse(); + tx2.setRollbackOnly(); + assertThat(tx2.isRollbackOnly()).isTrue(); + return Mono.empty(); + }); + }).as(StepVerifier::create).verifyComplete(); verify(connectionMock).rollbackTransaction(); verify(connectionMock).close(); @@ -481,16 +507,19 @@ class R2dbcTransactionManagerUnitTests { TransactionalOperator operator = TransactionalOperator.create(tm, definition); operator.execute(tx1 -> { + assertThat(tx1.hasTransaction()).isFalse(); assertThat(tx1.isNewTransaction()).isFalse(); + assertThat(tx1.isNested()).isFalse(); DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition(); innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); TransactionalOperator inner = TransactionalOperator.create(tm, innerDef); return inner.execute(tx2 -> { + assertThat(tx2.hasTransaction()).isTrue(); assertThat(tx2.isNewTransaction()).isTrue(); + assertThat(tx2.isNested()).isFalse(); return Mono.empty(); }); - }).as(StepVerifier::create) - .verifyComplete(); + }).as(StepVerifier::create).verifyComplete(); verify(connectionMock).commitTransaction(); verify(connectionMock).close(); @@ -505,17 +534,22 @@ class R2dbcTransactionManagerUnitTests { TransactionalOperator operator = TransactionalOperator.create(tm, definition); operator.execute(tx1 -> { - assertThat(tx1.isNewTransaction()).isFalse(); - DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition(); - innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - TransactionalOperator inner = TransactionalOperator.create(tm, innerDef); - return inner.execute(tx2 -> { - assertThat(tx2.isNewTransaction()).isTrue(); - tx2.setRollbackOnly(); - return Mono.empty(); - }); - }).as(StepVerifier::create) - .verifyComplete(); + assertThat(tx1.hasTransaction()).isFalse(); + assertThat(tx1.isNewTransaction()).isFalse(); + assertThat(tx1.isNested()).isFalse(); + DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition(); + innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + TransactionalOperator inner = TransactionalOperator.create(tm, innerDef); + return inner.execute(tx2 -> { + assertThat(tx2.hasTransaction()).isTrue(); + assertThat(tx2.isNewTransaction()).isTrue(); + assertThat(tx2.isNested()).isFalse(); + assertThat(tx2.isRollbackOnly()).isFalse(); + tx2.setRollbackOnly(); + assertThat(tx2.isRollbackOnly()).isTrue(); + return Mono.empty(); + }); + }).as(StepVerifier::create).verifyComplete(); verify(connectionMock).rollbackTransaction(); verify(connectionMock).close(); diff --git a/spring-tx/src/main/java/org/springframework/transaction/ConfigurableTransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/ConfigurableTransactionManager.java new file mode 100644 index 0000000000..8014f23fe0 --- /dev/null +++ b/spring-tx/src/main/java/org/springframework/transaction/ConfigurableTransactionManager.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2023 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.transaction; + +import java.util.Collection; + +/** + * Common configuration interface for transaction manager implementations. + * Provides registration facilities for {@link TransactionExecutionListener}. + * + * @author Juergen Hoeller + * @since 6.1 + * @see PlatformTransactionManager + * @see ReactiveTransactionManager + */ +public interface ConfigurableTransactionManager extends TransactionManager { + + /** + * Set the transaction execution listeners for begin/commit/rollback callbacks + * from this transaction manager. + * @see #addListener + */ + void setTransactionExecutionListeners(Collection listeners); + + /** + * Return the registered transaction execution listeners for this transaction manager. + * @see #setTransactionExecutionListeners + */ + Collection getTransactionExecutionListeners(); + + /** + * Conveniently register the given listener for begin/commit/rollback callbacks + * from this transaction manager. + * @see #getTransactionExecutionListeners() + */ + default void addListener(TransactionExecutionListener listener) { + getTransactionExecutionListeners().add(listener); + } + +} diff --git a/spring-tx/src/main/java/org/springframework/transaction/PlatformTransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/PlatformTransactionManager.java index 6122d0906d..fb937bf423 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/PlatformTransactionManager.java +++ b/spring-tx/src/main/java/org/springframework/transaction/PlatformTransactionManager.java @@ -42,6 +42,7 @@ import org.springframework.lang.Nullable; * @see org.springframework.transaction.support.TransactionTemplate * @see org.springframework.transaction.interceptor.TransactionInterceptor * @see org.springframework.transaction.ReactiveTransactionManager + * @see ConfigurableTransactionManager */ public interface PlatformTransactionManager extends TransactionManager { @@ -68,8 +69,7 @@ public interface PlatformTransactionManager extends TransactionManager { * @see TransactionDefinition#getTimeout * @see TransactionDefinition#isReadOnly */ - TransactionStatus getTransaction(@Nullable TransactionDefinition definition) - throws TransactionException; + TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; /** * Commit the given transaction, with regard to its status. If the transaction diff --git a/spring-tx/src/main/java/org/springframework/transaction/ReactiveTransaction.java b/spring-tx/src/main/java/org/springframework/transaction/ReactiveTransaction.java index 11a33e681b..76cfd8a918 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/ReactiveTransaction.java +++ b/spring-tx/src/main/java/org/springframework/transaction/ReactiveTransaction.java @@ -17,7 +17,7 @@ package org.springframework.transaction; /** - * Representation of an ongoing reactive transaction. + * Representation of an ongoing {@link ReactiveTransactionManager} transaction. * This is currently a marker interface extending {@link TransactionExecution} * but may acquire further methods in a future revision. * diff --git a/spring-tx/src/main/java/org/springframework/transaction/ReactiveTransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/ReactiveTransactionManager.java index d55a13472d..6be097faf7 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/ReactiveTransactionManager.java +++ b/spring-tx/src/main/java/org/springframework/transaction/ReactiveTransactionManager.java @@ -32,6 +32,7 @@ import org.springframework.lang.Nullable; * @see org.springframework.transaction.reactive.TransactionalOperator * @see org.springframework.transaction.interceptor.TransactionInterceptor * @see org.springframework.transaction.PlatformTransactionManager + * @see ConfigurableTransactionManager */ public interface ReactiveTransactionManager extends TransactionManager { diff --git a/spring-tx/src/main/java/org/springframework/transaction/TransactionExecution.java b/spring-tx/src/main/java/org/springframework/transaction/TransactionExecution.java index d5d0706187..ebce3987c2 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/TransactionExecution.java +++ b/spring-tx/src/main/java/org/springframework/transaction/TransactionExecution.java @@ -19,7 +19,8 @@ package org.springframework.transaction; /** * Common representation of the current state of a transaction. * Serves as base interface for {@link TransactionStatus} as well as - * {@link ReactiveTransaction}. + * {@link ReactiveTransaction}, and as of 6.1 also as transaction + * representation for {@link TransactionExecutionListener}. * * @author Juergen Hoeller * @since 5.2 @@ -27,29 +28,102 @@ package org.springframework.transaction; public interface TransactionExecution { /** - * Return whether the present transaction is new; otherwise participating - * in an existing transaction, or potentially not running in an actual - * transaction in the first place. + * Return the defined name of the transaction (possibly an empty String). + *

In case of Spring's declarative transactions, the exposed name will be + * the {@code fully-qualified class name + "." + method name} (by default). + *

The default implementation returns an empty String. + * @since 6.1 + * @see TransactionDefinition#getName() */ - boolean isNewTransaction(); + default String getTransactionName() { + return ""; + } + + /** + * Return whether there is an actual transaction active: this is meant to cover + * a new transaction as well as participation in an existing transaction, only + * returning {@code false} when not running in an actual transaction at all. + *

The default implementation returns {@code true}. + * @since 6.1 + * @see #isNewTransaction() + * @see #isNested() + * @see #isReadOnly() + */ + default boolean hasTransaction() { + return true; + } + + /** + * Return whether the transaction manager considers the present transaction + * as new; otherwise participating in an existing transaction, or potentially + * not running in an actual transaction in the first place. + *

This is primarily here for transaction manager state handling. + * Prefer the use of {@link #hasTransaction()} for application purposes + * since this is usually semantically appropriate. + *

The "new" status can be transaction manager specific, e.g. returning + * {@code true} for an actual nested transaction but potentially {@code false} + * for a savepoint-based nested transaction scope if the savepoint management + * is explicitly exposed (such as on {@link TransactionStatus}). A combined + * check for any kind of nested execution is provided by {@link #isNested()}. + *

The default implementation returns {@code true}. + * @see #hasTransaction() + * @see #isNested() + * @see TransactionStatus#hasSavepoint() + */ + default boolean isNewTransaction() { + return true; + } + + /** + * Return if this transaction executes in a nested fashion within another. + *

The default implementation returns {@code false}. + * @since 6.1 + * @see #hasTransaction() + * @see #isNewTransaction() + * @see TransactionDefinition#PROPAGATION_NESTED + */ + default boolean isNested() { + return false; + } + + /** + * Return if this transaction is defined as read-only transaction. + *

The default implementation returns {@code false}. + * @since 6.1 + * @see TransactionDefinition#isReadOnly() + */ + default boolean isReadOnly() { + return false; + } /** * Set the transaction rollback-only. This instructs the transaction manager * that the only possible outcome of the transaction may be a rollback, as * alternative to throwing an exception which would in turn trigger a rollback. + *

The default implementation throws an UnsupportedOperationException. + * @see #isRollbackOnly() */ - void setRollbackOnly(); + default void setRollbackOnly() { + throw new UnsupportedOperationException("setRollbackOnly not supported"); + } /** * Return whether the transaction has been marked as rollback-only * (either by the application or by the transaction infrastructure). + *

The default implementation returns {@code false}. + * @see #setRollbackOnly() */ - boolean isRollbackOnly(); + default boolean isRollbackOnly() { + return false; + } /** * Return whether this transaction is completed, that is, * whether it has already been committed or rolled back. + *

The default implementation returns {@code false}. */ - boolean isCompleted(); + default boolean isCompleted() { + return false; + } } diff --git a/spring-tx/src/main/java/org/springframework/transaction/TransactionExecutionListener.java b/spring-tx/src/main/java/org/springframework/transaction/TransactionExecutionListener.java new file mode 100644 index 0000000000..768c90cbdb --- /dev/null +++ b/spring-tx/src/main/java/org/springframework/transaction/TransactionExecutionListener.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2023 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.transaction; + +import org.springframework.lang.Nullable; + +/** + * Callback interface for stateless listening to transaction creation/completion steps + * in a transaction manager. This is primarily meant for observation and statistics; + * consider stateful transaction synchronizations for resource management purposes. + * + *

In contrast to synchronizations, the transaction execution listener contract is + * commonly supported for thread-bound transactions as well as reactive transactions. + * The callback-provided {@link TransactionExecution} object will be either a + * {@link TransactionStatus} (for a {@link PlatformTransactionManager} transaction) or + * a {@link ReactiveTransaction} (for a {@link ReactiveTransactionManager} transaction). + * + * @author Juergen Hoeller + * @since 6.1 + * @see ConfigurableTransactionManager#addListener + * @see org.springframework.transaction.support.TransactionSynchronizationManager#registerSynchronization + * @see org.springframework.transaction.reactive.TransactionSynchronizationManager#registerSynchronization + */ +public interface TransactionExecutionListener { + + /** + * Callback before the transaction begin step. + * @param transaction the current transaction + */ + default void beforeBegin(TransactionExecution transaction) { + } + + /** + * Callback after the transaction begin step. + * @param transaction the current transaction + * @param beginFailure an exception occurring during begin + * (or {@code null} after a successful begin step) + */ + default void afterBegin(TransactionExecution transaction, @Nullable Throwable beginFailure) { + } + + /** + * Callback before the transaction commit step. + * @param transaction the current transaction + */ + default void beforeCommit(TransactionExecution transaction) { + } + + /** + * Callback after the transaction commit step. + * @param transaction the current transaction + * @param commitFailure an exception occurring during commit + * (or {@code null} after a successful commit step) + */ + default void afterCommit(TransactionExecution transaction, @Nullable Throwable commitFailure) { + } + + /** + * Callback before the transaction rollback step. + * @param transaction the current transaction + */ + default void beforeRollback(TransactionExecution transaction) { + } + + /** + * Callback after the transaction rollback step. + * @param transaction the current transaction + * @param rollbackFailure an exception occurring during rollback + * (or {@code null} after a successful rollback step) + */ + default void afterRollback(TransactionExecution transaction, @Nullable Throwable rollbackFailure) { + } + +} diff --git a/spring-tx/src/main/java/org/springframework/transaction/TransactionStatus.java b/spring-tx/src/main/java/org/springframework/transaction/TransactionStatus.java index 5968c57fd7..01fcbe8f27 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/TransactionStatus.java +++ b/spring-tx/src/main/java/org/springframework/transaction/TransactionStatus.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -19,7 +19,8 @@ package org.springframework.transaction; import java.io.Flushable; /** - * Representation of the status of a transaction. + * Representation of an ongoing {@link PlatformTransactionManager} transaction. + * Extends the common {@link TransactionExecution} interface. * *

Transactional code can use this to retrieve status information, * and to programmatically request a rollback (instead of throwing @@ -44,12 +45,15 @@ public interface TransactionStatus extends TransactionExecution, SavepointManage *

This method is mainly here for diagnostic purposes, alongside * {@link #isNewTransaction()}. For programmatic handling of custom * savepoints, use the operations provided by {@link SavepointManager}. + *

The default implementation returns {@code false}. * @see #isNewTransaction() * @see #createSavepoint() * @see #rollbackToSavepoint(Object) * @see #releaseSavepoint(Object) */ - boolean hasSavepoint(); + default boolean hasSavepoint() { + return false; + } /** * Flush the underlying session to the datastore, if applicable: @@ -58,8 +62,10 @@ public interface TransactionStatus extends TransactionExecution, SavepointManage * transaction manager does not have a flush concept. A flush signal may * get applied to the primary resource or to transaction synchronizations, * depending on the underlying resource. + *

The default implementation is empty, considering flush as a no-op. */ @Override - void flush(); + default void flush() { + } } diff --git a/spring-tx/src/main/java/org/springframework/transaction/reactive/AbstractReactiveTransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/reactive/AbstractReactiveTransactionManager.java index ebeeeb5f20..0134bb4b5b 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/reactive/AbstractReactiveTransactionManager.java +++ b/spring-tx/src/main/java/org/springframework/transaction/reactive/AbstractReactiveTransactionManager.java @@ -19,6 +19,8 @@ package org.springframework.transaction.reactive; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; @@ -30,12 +32,14 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.lang.Nullable; +import org.springframework.transaction.ConfigurableTransactionManager; import org.springframework.transaction.IllegalTransactionStateException; import org.springframework.transaction.InvalidTimeoutException; import org.springframework.transaction.ReactiveTransaction; import org.springframework.transaction.ReactiveTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionExecutionListener; import org.springframework.transaction.TransactionSuspensionNotSupportedException; import org.springframework.transaction.UnexpectedRollbackException; @@ -77,10 +81,24 @@ import org.springframework.transaction.UnexpectedRollbackException; * @see TransactionSynchronizationManager */ @SuppressWarnings("serial") -public abstract class AbstractReactiveTransactionManager implements ReactiveTransactionManager, Serializable { +public abstract class AbstractReactiveTransactionManager + implements ReactiveTransactionManager, ConfigurableTransactionManager, Serializable { protected transient Log logger = LogFactory.getLog(getClass()); + private Collection transactionExecutionListeners = new ArrayList<>(); + + + @Override + public final void setTransactionExecutionListeners(Collection listeners) { + this.transactionExecutionListeners = listeners; + } + + @Override + public final Collection getTransactionExecutionListeners() { + return this.transactionExecutionListeners; + } + //--------------------------------------------------------------------- // Implementation of ReactiveTransactionManager @@ -99,8 +117,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran // Use defaults if no transaction definition given. TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults()); - return TransactionSynchronizationManager.forCurrentTransaction() - .flatMap(synchronizationManager -> { + return TransactionSynchronizationManager.forCurrentTransaction().flatMap(synchronizationManager -> { Object transaction = doGetTransaction(synchronizationManager); @@ -139,13 +156,16 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran return Mono.defer(() -> { GenericReactiveTransaction status = newReactiveTransaction( nestedSynchronizationManager, def, transaction, true, - debugEnabled, suspendedResources.orElse(null)); + false, debugEnabled, suspendedResources.orElse(null)); + this.transactionExecutionListeners.forEach(listener -> listener.beforeBegin(status)); return doBegin(nestedSynchronizationManager, transaction, def) .doOnSuccess(ignore -> prepareSynchronization(nestedSynchronizationManager, status, def)) + .doOnError(ex -> this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, ex))) .thenReturn(status); - }).onErrorResume(ErrorPredicates.RUNTIME_OR_ERROR, + }).doOnSuccess(status -> this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, null))) + .onErrorResume(ErrorPredicates.RUNTIME_OR_ERROR, ex -> resume(nestedSynchronizationManager, null, suspendedResources.orElse(null)) - .then(Mono.error(ex))); + .then(Mono.error(ex))); })); } else { @@ -190,9 +210,13 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran Mono suspendedResources = suspend(synchronizationManager, transaction); return suspendedResources.flatMap(suspendedResourcesHolder -> { GenericReactiveTransaction status = newReactiveTransaction(synchronizationManager, - definition, transaction, true, debugEnabled, suspendedResourcesHolder); - return doBegin(synchronizationManager, transaction, definition).doOnSuccess(ignore -> - prepareSynchronization(synchronizationManager, status, definition)).thenReturn(status) + definition, transaction, true, false, debugEnabled, suspendedResourcesHolder); + this.transactionExecutionListeners.forEach(listener -> listener.beforeBegin(status)); + return doBegin(synchronizationManager, transaction, definition) + .doOnSuccess(ignore -> prepareSynchronization(synchronizationManager, status, definition)) + .doOnError(ex -> this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, ex))) + .thenReturn(status) + .doOnSuccess(ignore -> this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, null))) .onErrorResume(ErrorPredicates.RUNTIME_OR_ERROR, beginEx -> resumeAfterBeginException(synchronizationManager, transaction, suspendedResourcesHolder, beginEx) .then(Mono.error(beginEx))); @@ -205,7 +229,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran } // Nested transaction through nested begin and commit/rollback calls. GenericReactiveTransaction status = newReactiveTransaction(synchronizationManager, - definition, transaction, true, debugEnabled, null); + definition, transaction, true, true, debugEnabled, null); return doBegin(synchronizationManager, transaction, definition).doOnSuccess(ignore -> prepareSynchronization(synchronizationManager, status, definition)).thenReturn(status); } @@ -228,7 +252,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran @Nullable Object transaction, boolean newTransaction, boolean debug, @Nullable Object suspendedResources) { GenericReactiveTransaction status = newReactiveTransaction(synchronizationManager, - definition, transaction, newTransaction, debug, suspendedResources); + definition, transaction, newTransaction, false, debug, suspendedResources); prepareSynchronization(synchronizationManager, status, definition); return status; } @@ -238,11 +262,12 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran */ private GenericReactiveTransaction newReactiveTransaction( TransactionSynchronizationManager synchronizationManager, TransactionDefinition definition, - @Nullable Object transaction, boolean newTransaction, boolean debug, @Nullable Object suspendedResources) { + @Nullable Object transaction, boolean newTransaction, boolean nested, boolean debug, + @Nullable Object suspendedResources) { - return new GenericReactiveTransaction(transaction, newTransaction, - !synchronizationManager.isSynchronizationActive(), - definition.isReadOnly(), debug, suspendedResources); + return new GenericReactiveTransaction(definition.getName(), transaction, + newTransaction, !synchronizationManager.isSynchronizationActive(), + nested, definition.isReadOnly(), debug, suspendedResources); } /** @@ -438,6 +463,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran if (status.isDebug()) { logger.debug("Initiating transaction commit"); } + this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status)); return doCommit(synchronizationManager, status); } return Mono.empty(); @@ -449,11 +475,21 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran Mono result = propagateException; if (ErrorPredicates.UNEXPECTED_ROLLBACK.test(ex)) { result = triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_ROLLED_BACK) - .then(propagateException); + .then(Mono.defer(() -> { + if (status.isNewTransaction()) { + this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null)); + } + return propagateException; + })); } else if (ErrorPredicates.TRANSACTION_EXCEPTION.test(ex)) { result = triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_UNKNOWN) - .then(propagateException); + .then(Mono.defer(() -> { + if (status.isNewTransaction()) { + this.transactionExecutionListeners.forEach(listener -> listener.afterCommit(status, ex)); + } + return propagateException; + })); } else if (ErrorPredicates.RUNTIME_OR_ERROR.test(ex)) { Mono mono; @@ -471,7 +507,13 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran }) .then(Mono.defer(() -> triggerAfterCommit(synchronizationManager, status).onErrorResume(ex -> triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_COMMITTED).then(Mono.error(ex))) - .then(triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_COMMITTED)))); + .then(triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_COMMITTED)) + .then(Mono.defer(() -> { + if (status.isNewTransaction()) { + this.transactionExecutionListeners.forEach(listener -> listener.afterCommit(status, null)); + } + return Mono.empty(); + })))); return commit .onErrorResume(ex -> cleanupAfterCompletion(synchronizationManager, status) @@ -510,6 +552,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran if (status.isDebug()) { logger.debug("Initiating transaction rollback"); } + this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status)); return doRollback(synchronizationManager, status); } else { @@ -528,10 +571,22 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran } })).onErrorResume(ErrorPredicates.RUNTIME_OR_ERROR, ex -> triggerAfterCompletion( synchronizationManager, status, TransactionSynchronization.STATUS_UNKNOWN) - .then(Mono.error(ex))) + .then(Mono.defer(() -> { + if (status.isNewTransaction()) { + this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, ex)); + } + return Mono.empty(); + })) + .then(Mono.error(ex))) .then(Mono.defer(() -> triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_ROLLED_BACK))) + .then(Mono.defer(() -> { + if (status.isNewTransaction()) { + this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null)); + } + return Mono.empty(); + })) .onErrorResume(ex -> cleanupAfterCompletion(synchronizationManager, status).then(Mono.error(ex))) - .then(cleanupAfterCompletion(synchronizationManager, status)); + .then(cleanupAfterCompletion(synchronizationManager, status)); } /** @@ -561,8 +616,16 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran }).onErrorResume(ErrorPredicates.RUNTIME_OR_ERROR, rbex -> { logger.error("Commit exception overridden by rollback exception", ex); return triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_UNKNOWN) - .then(Mono.error(rbex)); - }).then(triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_ROLLED_BACK)); + .then(Mono.defer(() -> { + this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, rbex)); + return Mono.empty(); + })) + .then(Mono.error(rbex)); + }).then(triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_ROLLED_BACK)) + .then(Mono.defer(() -> { + this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null)); + return Mono.empty(); + })); } diff --git a/spring-tx/src/main/java/org/springframework/transaction/reactive/GenericReactiveTransaction.java b/spring-tx/src/main/java/org/springframework/transaction/reactive/GenericReactiveTransaction.java index 702392368a..f4c9602a1c 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/reactive/GenericReactiveTransaction.java +++ b/spring-tx/src/main/java/org/springframework/transaction/reactive/GenericReactiveTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -40,6 +40,9 @@ import org.springframework.util.Assert; */ public class GenericReactiveTransaction implements ReactiveTransaction { + @Nullable + private final String transactionName; + @Nullable private final Object transaction; @@ -47,6 +50,8 @@ public class GenericReactiveTransaction implements ReactiveTransaction { private final boolean newSynchronization; + private final boolean nested; + private final boolean readOnly; private final boolean debug; @@ -61,6 +66,7 @@ public class GenericReactiveTransaction implements ReactiveTransaction { /** * Create a new {@code DefaultReactiveTransactionStatus} instance. + * @param transactionName the defined name of the transaction * @param transaction underlying transaction object that can hold state * for the internal transaction implementation * @param newTransaction if the transaction is new, otherwise participating @@ -73,19 +79,35 @@ public class GenericReactiveTransaction implements ReactiveTransaction { * debug logging should be enabled. * @param suspendedResources a holder for resources that have been suspended * for this transaction, if any + * @since 6.1 */ public GenericReactiveTransaction( - @Nullable Object transaction, boolean newTransaction, boolean newSynchronization, - boolean readOnly, boolean debug, @Nullable Object suspendedResources) { + @Nullable String transactionName, @Nullable Object transaction, boolean newTransaction, + boolean newSynchronization, boolean nested, boolean readOnly, boolean debug, + @Nullable Object suspendedResources) { + this.transactionName = transactionName; this.transaction = transaction; this.newTransaction = newTransaction; this.newSynchronization = newSynchronization; + this.nested = nested; this.readOnly = readOnly; this.debug = debug; this.suspendedResources = suspendedResources; } + @Deprecated(since = "6.1", forRemoval = true) + public GenericReactiveTransaction(@Nullable Object transaction, boolean newTransaction, + boolean newSynchronization, boolean readOnly, boolean debug, @Nullable Object suspendedResources) { + + this(null, transaction, newTransaction, newSynchronization, false, readOnly, debug, suspendedResources); + } + + + @Override + public String getTransactionName() { + return (this.transactionName != null ? this.transactionName : ""); + } /** * Return the underlying transaction object. @@ -96,9 +118,7 @@ public class GenericReactiveTransaction implements ReactiveTransaction { return this.transaction; } - /** - * Return whether there is an actual transaction active. - */ + @Override public boolean hasTransaction() { return (this.transaction != null); } @@ -109,16 +129,18 @@ public class GenericReactiveTransaction implements ReactiveTransaction { } /** - * Return if a new transaction synchronization has been opened - * for this transaction. + * Return if a new transaction synchronization has been opened for this transaction. */ public boolean isNewSynchronization() { return this.newSynchronization; } - /** - * Return if this transaction is defined as read-only transaction. - */ + @Override + public boolean isNested() { + return this.nested; + } + + @Override public boolean isReadOnly() { return this.readOnly; } @@ -143,6 +165,9 @@ public class GenericReactiveTransaction implements ReactiveTransaction { @Override public void setRollbackOnly() { + if (this.completed) { + throw new IllegalStateException("Transaction completed"); + } this.rollbackOnly = true; } diff --git a/spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java index 6b202d3112..97555dcabb 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java +++ b/spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java @@ -19,6 +19,8 @@ package org.springframework.transaction.support; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -26,12 +28,14 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.lang.Nullable; +import org.springframework.transaction.ConfigurableTransactionManager; import org.springframework.transaction.IllegalTransactionStateException; import org.springframework.transaction.InvalidTimeoutException; import org.springframework.transaction.NestedTransactionNotSupportedException; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionExecutionListener; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionSuspensionNotSupportedException; import org.springframework.transaction.UnexpectedRollbackException; @@ -82,7 +86,8 @@ import org.springframework.util.Assert; * @see org.springframework.transaction.jta.JtaTransactionManager */ @SuppressWarnings("serial") -public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { +public abstract class AbstractPlatformTransactionManager + implements PlatformTransactionManager, ConfigurableTransactionManager, Serializable { /** * Always activate transaction synchronization, even for "empty" transactions @@ -136,6 +141,8 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran private boolean rollbackOnCommitFailure = false; + private Collection transactionExecutionListeners = new ArrayList<>(); + /** * Set the transaction synchronization by the name of the corresponding constant @@ -339,6 +346,16 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran return this.rollbackOnCommitFailure; } + @Override + public final void setTransactionExecutionListeners(Collection listeners) { + this.transactionExecutionListeners = listeners; + } + + @Override + public final Collection getTransactionExecutionListeners() { + return this.transactionExecutionListeners; + } + //--------------------------------------------------------------------- // Implementation of PlatformTransactionManager @@ -385,7 +402,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def); } try { - return startTransaction(def, transaction, debugEnabled, suspendedResources); + return startTransaction(def, transaction, false, debugEnabled, suspendedResources); } catch (RuntimeException | Error ex) { resume(null, suspendedResources); @@ -403,20 +420,6 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran } } - /** - * Start a new transaction. - */ - private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction, - boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) { - - boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); - DefaultTransactionStatus status = newTransactionStatus( - definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); - doBegin(transaction, definition); - prepareSynchronization(status, definition); - return status; - } - /** * Create a TransactionStatus for an existing transaction. */ @@ -446,7 +449,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran } SuspendedResourcesHolder suspendedResources = suspend(transaction); try { - return startTransaction(definition, transaction, debugEnabled, suspendedResources); + return startTransaction(definition, transaction, false, debugEnabled, suspendedResources); } catch (RuntimeException | Error beginEx) { resumeAfterBeginException(transaction, suspendedResources, beginEx); @@ -467,16 +470,24 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran // Create savepoint within existing Spring-managed transaction, // through the SavepointManager API implemented by TransactionStatus. // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization. - DefaultTransactionStatus status = - prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null); - status.createAndHoldSavepoint(); + DefaultTransactionStatus status = newTransactionStatus( + definition, transaction, false, false, true, debugEnabled, null); + this.transactionExecutionListeners.forEach(listener -> listener.beforeBegin(status)); + try { + status.createAndHoldSavepoint(); + } + catch (RuntimeException | Error ex) { + this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, ex)); + throw ex; + } + this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, null)); return status; } else { // Nested transaction through nested begin and commit/rollback calls. // Usually only for JTA: Spring synchronization might get activated here // in case of a pre-existing JTA transaction. - return startTransaction(definition, transaction, debugEnabled, null); + return startTransaction(definition, transaction, true, debugEnabled, null); } } @@ -506,18 +517,40 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null); } + /** + * Start a new transaction. + */ + private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction, + boolean nested, boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) { + + boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + DefaultTransactionStatus status = newTransactionStatus( + definition, transaction, true, newSynchronization, nested, debugEnabled, suspendedResources); + this.transactionExecutionListeners.forEach(listener -> listener.beforeBegin(status)); + try { + doBegin(transaction, definition); + } + catch (RuntimeException | Error ex) { + this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, ex)); + throw ex; + } + prepareSynchronization(status, definition); + this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, null)); + return status; + } + /** * Create a new TransactionStatus for the given arguments, * also initializing transaction synchronization as appropriate. * @see #newTransactionStatus * @see #prepareTransactionStatus */ - protected final DefaultTransactionStatus prepareTransactionStatus( + private DefaultTransactionStatus prepareTransactionStatus( TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction, boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) { DefaultTransactionStatus status = newTransactionStatus( - definition, transaction, newTransaction, newSynchronization, debug, suspendedResources); + definition, transaction, newTransaction, newSynchronization, false, debug, suspendedResources); prepareSynchronization(status, definition); return status; } @@ -525,21 +558,20 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran /** * Create a TransactionStatus instance for the given arguments. */ - protected DefaultTransactionStatus newTransactionStatus( + private DefaultTransactionStatus newTransactionStatus( TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction, - boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) { + boolean newSynchronization, boolean nested, boolean debug, @Nullable Object suspendedResources) { boolean actualNewSynchronization = newSynchronization && !TransactionSynchronizationManager.isSynchronizationActive(); - return new DefaultTransactionStatus( - transaction, newTransaction, actualNewSynchronization, - definition.isReadOnly(), debug, suspendedResources); + return new DefaultTransactionStatus(definition.getName(), transaction, newTransaction, + actualNewSynchronization, nested, definition.isReadOnly(), debug, suspendedResources); } /** * Initialize transaction synchronization as appropriate. */ - protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) { + private void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) { if (status.isNewSynchronization()) { TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction()); TransactionSynchronizationManager.setCurrentTransactionIsolationLevel( @@ -734,6 +766,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran private void processCommit(DefaultTransactionStatus status) throws TransactionException { try { boolean beforeCompletionInvoked = false; + boolean commitListenerInvoked = false; try { boolean unexpectedRollback = false; @@ -747,6 +780,8 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran logger.debug("Releasing transaction savepoint"); } unexpectedRollback = status.isGlobalRollbackOnly(); + this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status)); + commitListenerInvoked = true; status.releaseHeldSavepoint(); } else if (status.isNewTransaction()) { @@ -754,6 +789,8 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran logger.debug("Initiating transaction commit"); } unexpectedRollback = status.isGlobalRollbackOnly(); + this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status)); + commitListenerInvoked = true; doCommit(status); } else if (isFailEarlyOnGlobalRollbackOnly()) { @@ -768,17 +805,19 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran } } catch (UnexpectedRollbackException ex) { - // can only be caused by doCommit triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null)); throw ex; } catch (TransactionException ex) { - // can only be caused by doCommit if (isRollbackOnCommitFailure()) { doRollbackOnCommitException(status, ex); } else { triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + if (commitListenerInvoked) { + this.transactionExecutionListeners.forEach(listener -> listener.afterCommit(status, ex)); + } } throw ex; } @@ -797,6 +836,9 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran } finally { triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); + if (commitListenerInvoked) { + this.transactionExecutionListeners.forEach(listener -> listener.afterCommit(status, null)); + } } } @@ -832,6 +874,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran private void processRollback(DefaultTransactionStatus status, boolean unexpected) { try { boolean unexpectedRollback = unexpected; + boolean rollbackListenerInvoked = false; try { triggerBeforeCompletion(status); @@ -840,12 +883,16 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran if (status.isDebug()) { logger.debug("Rolling back transaction to savepoint"); } + this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status)); + rollbackListenerInvoked = true; status.rollbackToHeldSavepoint(); } else if (status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Initiating transaction rollback"); } + this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status)); + rollbackListenerInvoked = true; doRollback(status); } else { @@ -874,10 +921,16 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran } catch (RuntimeException | Error ex) { triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + if (rollbackListenerInvoked) { + this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, ex)); + } throw ex; } triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + if (rollbackListenerInvoked) { + this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null)); + } // Raise UnexpectedRollbackException if we had a global rollback-only marker if (unexpectedRollback) { @@ -915,9 +968,11 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran catch (RuntimeException | Error rbex) { logger.error("Commit exception overridden by rollback exception", ex); triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, rbex)); throw rbex; } triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null)); } diff --git a/spring-tx/src/main/java/org/springframework/transaction/support/AbstractTransactionStatus.java b/spring-tx/src/main/java/org/springframework/transaction/support/AbstractTransactionStatus.java index f6abfdfda6..e0ce47b45b 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/support/AbstractTransactionStatus.java +++ b/spring-tx/src/main/java/org/springframework/transaction/support/AbstractTransactionStatus.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -60,6 +60,9 @@ public abstract class AbstractTransactionStatus implements TransactionStatus { @Override public void setRollbackOnly() { + if (this.completed) { + throw new IllegalStateException("Transaction completed"); + } this.rollbackOnly = true; } @@ -216,16 +219,4 @@ public abstract class AbstractTransactionStatus implements TransactionStatus { throw new NestedTransactionNotSupportedException("This transaction does not support savepoints"); } - - //--------------------------------------------------------------------- - // Flushing support - //--------------------------------------------------------------------- - - /** - * This implementation is empty, considering flush as a no-op. - */ - @Override - public void flush() { - } - } diff --git a/spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionStatus.java b/spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionStatus.java index ff9c95691a..5b92724b5e 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionStatus.java +++ b/spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionStatus.java @@ -50,6 +50,9 @@ import org.springframework.util.Assert; */ public class DefaultTransactionStatus extends AbstractTransactionStatus { + @Nullable + private final String transactionName; + @Nullable private final Object transaction; @@ -57,6 +60,8 @@ public class DefaultTransactionStatus extends AbstractTransactionStatus { private final boolean newSynchronization; + private final boolean nested; + private final boolean readOnly; private final boolean debug; @@ -67,6 +72,7 @@ public class DefaultTransactionStatus extends AbstractTransactionStatus { /** * Create a new {@code DefaultTransactionStatus} instance. + * @param transactionName the defined name of the transaction * @param transaction underlying transaction object that can hold state * for the internal transaction implementation * @param newTransaction if the transaction is new, otherwise participating @@ -79,19 +85,35 @@ public class DefaultTransactionStatus extends AbstractTransactionStatus { * debug logging should be enabled. * @param suspendedResources a holder for resources that have been suspended * for this transaction, if any + * @since 6.1 */ public DefaultTransactionStatus( - @Nullable Object transaction, boolean newTransaction, boolean newSynchronization, - boolean readOnly, boolean debug, @Nullable Object suspendedResources) { + @Nullable String transactionName, @Nullable Object transaction, boolean newTransaction, + boolean newSynchronization, boolean nested, boolean readOnly, boolean debug, + @Nullable Object suspendedResources) { + this.transactionName = transactionName; this.transaction = transaction; this.newTransaction = newTransaction; this.newSynchronization = newSynchronization; + this.nested = nested; this.readOnly = readOnly; this.debug = debug; this.suspendedResources = suspendedResources; } + @Deprecated(since = "6.1", forRemoval = true) + public DefaultTransactionStatus(@Nullable Object transaction, boolean newTransaction, + boolean newSynchronization, boolean readOnly, boolean debug, @Nullable Object suspendedResources) { + + this(null, transaction, newTransaction, newSynchronization, false, readOnly, debug, suspendedResources); + } + + + @Override + public String getTransactionName() { + return (this.transactionName != null ? this.transactionName : ""); + } /** * Return the underlying transaction object. @@ -102,9 +124,7 @@ public class DefaultTransactionStatus extends AbstractTransactionStatus { return this.transaction; } - /** - * Return whether there is an actual transaction active. - */ + @Override public boolean hasTransaction() { return (this.transaction != null); } @@ -115,16 +135,18 @@ public class DefaultTransactionStatus extends AbstractTransactionStatus { } /** - * Return if a new transaction synchronization has been opened - * for this transaction. + * Return if a new transaction synchronization has been opened for this transaction. */ public boolean isNewSynchronization() { return this.newSynchronization; } - /** - * Return if this transaction is defined as read-only transaction. - */ + @Override + public boolean isNested() { + return this.nested; + } + + @Override public boolean isReadOnly() { return this.readOnly; } diff --git a/spring-tx/src/main/java/org/springframework/transaction/support/SmartTransactionObject.java b/spring-tx/src/main/java/org/springframework/transaction/support/SmartTransactionObject.java index ba5537c443..b1bf14522c 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/support/SmartTransactionObject.java +++ b/spring-tx/src/main/java/org/springframework/transaction/support/SmartTransactionObject.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2023 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. @@ -23,29 +23,34 @@ import java.io.Flushable; * return an internal rollback-only marker, typically from another * transaction that has participated and marked it as rollback-only. * - *

Autodetected by DefaultTransactionStatus, to always return a - * current rollbackOnly flag even if not resulting from the current + *

Autodetected by {@link DefaultTransactionStatus} in order to always + * return a current rollbackOnly flag even if not resulting from the current * TransactionStatus. * * @author Juergen Hoeller * @since 1.1 - * @see DefaultTransactionStatus#isRollbackOnly + * @see DefaultTransactionStatus#isGlobalRollbackOnly() */ public interface SmartTransactionObject extends Flushable { /** * Return whether the transaction is internally marked as rollback-only. * Can, for example, check the JTA UserTransaction. + *

The default implementation returns {@code false}. * @see jakarta.transaction.UserTransaction#getStatus * @see jakarta.transaction.Status#STATUS_MARKED_ROLLBACK */ - boolean isRollbackOnly(); + default boolean isRollbackOnly() { + return false; + } /** * Flush the underlying sessions to the datastore, if applicable: * for example, all affected Hibernate/JPA sessions. + *

The default implementation is empty, considering flush as a no-op. */ @Override - void flush(); + default void flush() { + } } diff --git a/spring-tx/src/test/java/org/springframework/transaction/JtaTransactionManagerTests.java b/spring-tx/src/test/java/org/springframework/transaction/JtaTransactionManagerTests.java index cdcd6e0182..c4ba1d2a6f 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/JtaTransactionManagerTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/JtaTransactionManagerTests.java @@ -72,10 +72,17 @@ public class JtaTransactionManagerTests { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { // something transactional + assertThat(status.getTransactionName()).isEqualTo("txName"); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.isReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); TransactionSynchronizationManager.registerSynchronization(synch); assertThat(TransactionSynchronizationManager.getCurrentTransactionName()).isEqualTo("txName"); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); + assertThat(status.isRollbackOnly()).isFalse(); + assertThat(status.isCompleted()).isFalse(); } }); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); @@ -105,8 +112,15 @@ public class JtaTransactionManagerTests { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { // something transactional + assertThat(status.getTransactionName()).isEmpty(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.isReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); TransactionSynchronizationManager.registerSynchronization(synch); + assertThat(status.isRollbackOnly()).isFalse(); + assertThat(status.isCompleted()).isFalse(); } }); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); @@ -134,7 +148,14 @@ public class JtaTransactionManagerTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { + assertThat(status.getTransactionName()).isEmpty(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.isReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); + assertThat(status.isRollbackOnly()).isFalse(); + assertThat(status.isCompleted()).isFalse(); } }); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); @@ -160,11 +181,18 @@ public class JtaTransactionManagerTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { + assertThat(status.getTransactionName()).isEqualTo("txName"); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.isReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); TransactionSynchronizationManager.registerSynchronization(synch); assertThat(TransactionSynchronizationManager.getCurrentTransactionName()).isEqualTo("txName"); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); status.setRollbackOnly(); + assertThat(status.isRollbackOnly()).isTrue(); + assertThat(status.isCompleted()).isFalse(); } }); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); @@ -192,9 +220,16 @@ public class JtaTransactionManagerTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { + assertThat(status.getTransactionName()).isEmpty(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.isReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); TransactionSynchronizationManager.registerSynchronization(synch); status.setRollbackOnly(); + assertThat(status.isRollbackOnly()).isTrue(); + assertThat(status.isCompleted()).isFalse(); } }); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); @@ -221,8 +256,16 @@ public class JtaTransactionManagerTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { + assertThat(status.getTransactionName()).isEmpty(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.isReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); + assertThat(status.isRollbackOnly()).isFalse(); status.setRollbackOnly(); + assertThat(status.isRollbackOnly()).isTrue(); + assertThat(status.isCompleted()).isFalse(); } }); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); @@ -808,6 +851,13 @@ public class JtaTransactionManagerTests { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { // something transactional + assertThat(status.getTransactionName()).isEmpty(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isTrue(); + assertThat(status.isReadOnly()).isFalse(); + assertThat(status.isRollbackOnly()).isFalse(); + assertThat(status.isCompleted()).isFalse(); } }); @@ -859,8 +909,11 @@ public class JtaTransactionManagerTests { given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION); willThrow(new SystemException("system exception")).given(ut).begin(); + JtaTransactionManager ptm = newJtaTransactionManager(ut); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + ptm.addListener(tl); + assertThatExceptionOfType(CannotCreateTransactionException.class).isThrownBy(() -> { - JtaTransactionManager ptm = newJtaTransactionManager(ut); TransactionTemplate tt = new TransactionTemplate(ptm); tt.execute(new TransactionCallbackWithoutResult() { @Override @@ -869,6 +922,16 @@ public class JtaTransactionManagerTests { } }); }); + + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beginFailure).isInstanceOf(CannotCreateTransactionException.class); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.commitFailure).isNull(); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isFalse(); + assertThat(tl.rollbackFailure).isNull(); } @Test @@ -878,8 +941,11 @@ public class JtaTransactionManagerTests { Status.STATUS_ACTIVE, Status.STATUS_ACTIVE); willThrow(new RollbackException("unexpected rollback")).given(ut).commit(); + JtaTransactionManager ptm = newJtaTransactionManager(ut); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + ptm.addListener(tl); + assertThatExceptionOfType(UnexpectedRollbackException.class).isThrownBy(() -> { - JtaTransactionManager ptm = newJtaTransactionManager(ut); TransactionTemplate tt = new TransactionTemplate(ptm); tt.execute(new TransactionCallbackWithoutResult() { @Override @@ -896,6 +962,16 @@ public class JtaTransactionManagerTests { }); }); + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beginFailure).isNull(); + assertThat(tl.beforeCommitCalled).isTrue(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.commitFailure).isNull(); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isTrue(); + assertThat(tl.rollbackFailure).isNull(); + verify(ut).begin(); } @@ -1023,8 +1099,11 @@ public class JtaTransactionManagerTests { Status.STATUS_ACTIVE, Status.STATUS_ACTIVE); willThrow(new SystemException("system exception")).given(ut).commit(); + JtaTransactionManager ptm = newJtaTransactionManager(ut); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + ptm.addListener(tl); + assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() -> { - JtaTransactionManager ptm = newJtaTransactionManager(ut); TransactionTemplate tt = new TransactionTemplate(ptm); tt.execute(new TransactionCallbackWithoutResult() { @Override @@ -1041,6 +1120,16 @@ public class JtaTransactionManagerTests { }); }); + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beginFailure).isNull(); + assertThat(tl.beforeCommitCalled).isTrue(); + assertThat(tl.afterCommitCalled).isTrue(); + assertThat(tl.commitFailure).isInstanceOf(TransactionSystemException.class); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isFalse(); + assertThat(tl.rollbackFailure).isNull(); + verify(ut).begin(); } @@ -1050,8 +1139,11 @@ public class JtaTransactionManagerTests { given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE); willThrow(new SystemException("system exception")).given(ut).rollback(); + JtaTransactionManager ptm = newJtaTransactionManager(ut); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + ptm.addListener(tl); + assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() -> { - JtaTransactionManager ptm = newJtaTransactionManager(ut); TransactionTemplate tt = new TransactionTemplate(ptm); tt.execute(new TransactionCallbackWithoutResult() { @Override @@ -1068,6 +1160,16 @@ public class JtaTransactionManagerTests { }); }); + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beginFailure).isNull(); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.commitFailure).isNull(); + assertThat(tl.beforeRollbackCalled).isTrue(); + assertThat(tl.afterRollbackCalled).isTrue(); + assertThat(tl.rollbackFailure).isInstanceOf(TransactionSystemException.class); + verify(ut).begin(); } diff --git a/spring-tx/src/test/java/org/springframework/transaction/TestTransactionExecutionListener.java b/spring-tx/src/test/java/org/springframework/transaction/TestTransactionExecutionListener.java new file mode 100644 index 0000000000..13119d9a51 --- /dev/null +++ b/spring-tx/src/test/java/org/springframework/transaction/TestTransactionExecutionListener.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2023 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.transaction; + +import org.springframework.lang.Nullable; + +/** + * @author Juergen Hoeller + * @since 6.1 + */ +public class TestTransactionExecutionListener implements TransactionExecutionListener { + + public boolean beforeBeginCalled; + + public boolean afterBeginCalled; + + @Nullable + public Throwable beginFailure; + + public boolean beforeCommitCalled; + + public boolean afterCommitCalled; + + @Nullable + public Throwable commitFailure; + + public boolean beforeRollbackCalled; + + public boolean afterRollbackCalled; + + @Nullable + public Throwable rollbackFailure; + + + @Override + public void beforeBegin(TransactionExecution transactionState) { + this.beforeBeginCalled = true; + } + + @Override + public void afterBegin(TransactionExecution transactionState, @Nullable Throwable beginFailure) { + this.afterBeginCalled = true; + this.beginFailure = beginFailure; + } + + @Override + public void beforeCommit(TransactionExecution transactionState) { + this.beforeCommitCalled = true; + } + + @Override + public void afterCommit(TransactionExecution transactionState, @Nullable Throwable commitFailure) { + this.afterCommitCalled = true; + this.commitFailure = commitFailure; + } + + @Override + public void beforeRollback(TransactionExecution transactionState) { + this.beforeRollbackCalled = true; + } + + @Override + public void afterRollback(TransactionExecution transactionState, @Nullable Throwable rollbackFailure) { + this.afterRollbackCalled = true; + this.rollbackFailure = rollbackFailure; + } + +} diff --git a/spring-tx/src/test/java/org/springframework/transaction/reactive/ReactiveTestTransactionManager.java b/spring-tx/src/test/java/org/springframework/transaction/reactive/ReactiveTestTransactionManager.java index 852dbfbffc..3a4dd0184f 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/reactive/ReactiveTestTransactionManager.java +++ b/spring-tx/src/test/java/org/springframework/transaction/reactive/ReactiveTestTransactionManager.java @@ -16,8 +16,11 @@ package org.springframework.transaction.reactive; +import java.util.function.Function; + import reactor.core.publisher.Mono; +import org.springframework.lang.Nullable; import org.springframework.transaction.CannotCreateTransactionException; import org.springframework.transaction.ReactiveTransactionManager; import org.springframework.transaction.TransactionDefinition; @@ -26,6 +29,7 @@ import org.springframework.transaction.TransactionDefinition; * Test implementation of a {@link ReactiveTransactionManager}. * * @author Mark Paluch + * @author Juergen Hoeller */ @SuppressWarnings("serial") class ReactiveTestTransactionManager extends AbstractReactiveTransactionManager { @@ -36,7 +40,11 @@ class ReactiveTestTransactionManager extends AbstractReactiveTransactionManager private final boolean canCreateTransaction; - private final boolean forceFailOnCommit; + @Nullable + private Function forceFailOnCommit; + + @Nullable + private Function forceFailOnRollback; protected boolean begin = false; @@ -50,13 +58,15 @@ class ReactiveTestTransactionManager extends AbstractReactiveTransactionManager ReactiveTestTransactionManager(boolean existingTransaction, boolean canCreateTransaction) { - this(existingTransaction, canCreateTransaction, false); - } - - ReactiveTestTransactionManager(boolean existingTransaction, boolean canCreateTransaction, boolean forceFailOnCommit) { this.existingTransaction = existingTransaction; this.canCreateTransaction = canCreateTransaction; + } + + ReactiveTestTransactionManager(boolean existingTransaction, @Nullable Function forceFailOnCommit, @Nullable Function forceFailOnRollback) { + this.existingTransaction = existingTransaction; + this.canCreateTransaction = true; this.forceFailOnCommit = forceFailOnCommit; + this.forceFailOnRollback = forceFailOnRollback; } @@ -88,8 +98,8 @@ class ReactiveTestTransactionManager extends AbstractReactiveTransactionManager } return Mono.fromRunnable(() -> { this.commit = true; - if (this.forceFailOnCommit) { - throw new IllegalArgumentException("Forced failure on commit"); + if (this.forceFailOnCommit != null) { + throw this.forceFailOnCommit.apply("Forced failure on commit"); } }); } @@ -99,7 +109,12 @@ class ReactiveTestTransactionManager extends AbstractReactiveTransactionManager if (!TRANSACTION.equals(status.getTransaction())) { return Mono.error(new IllegalArgumentException("Not the same transaction object")); } - return Mono.fromRunnable(() -> this.rollback = true); + return Mono.fromRunnable(() -> { + this.rollback = true; + if (this.forceFailOnRollback != null) { + throw this.forceFailOnRollback.apply("Forced failure on rollback"); + } + }); } @Override diff --git a/spring-tx/src/test/java/org/springframework/transaction/reactive/ReactiveTransactionSupportTests.java b/spring-tx/src/test/java/org/springframework/transaction/reactive/ReactiveTransactionSupportTests.java index e99a38281c..b6225f6f37 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/reactive/ReactiveTransactionSupportTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/reactive/ReactiveTransactionSupportTests.java @@ -21,10 +21,14 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.transaction.CannotCreateTransactionException; import org.springframework.transaction.IllegalTransactionStateException; import org.springframework.transaction.ReactiveTransaction; import org.springframework.transaction.ReactiveTransactionManager; +import org.springframework.transaction.TestTransactionExecutionListener; import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionSystemException; import org.springframework.transaction.support.DefaultTransactionDefinition; import static org.assertj.core.api.Assertions.assertThat; @@ -86,6 +90,9 @@ public class ReactiveTransactionSupportTests { @Test public void commitWithoutExistingTransaction() { ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::commit) .contextWrite(TransactionContextManager.createTransactionContext()) .as(StepVerifier::create).verifyComplete(); @@ -95,11 +102,21 @@ public class ReactiveTransactionSupportTests { assertHasNoRollback(tm); assertHasNotSetRollbackOnly(tm); assertHasCleanedUp(tm); + + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beforeCommitCalled).isTrue(); + assertThat(tl.afterCommitCalled).isTrue(); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isFalse(); } @Test public void rollbackWithoutExistingTransaction() { ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::rollback) .contextWrite(TransactionContextManager.createTransactionContext()).as(StepVerifier::create) .verifyComplete(); @@ -109,11 +126,21 @@ public class ReactiveTransactionSupportTests { assertHasRolledBack(tm); assertHasNotSetRollbackOnly(tm); assertHasCleanedUp(tm); + + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.beforeRollbackCalled).isTrue(); + assertThat(tl.afterRollbackCalled).isTrue(); } @Test public void rollbackOnlyWithoutExistingTransaction() { ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + tm.getReactiveTransaction(new DefaultTransactionDefinition()).doOnNext(ReactiveTransaction::setRollbackOnly) .flatMap(tm::commit) .contextWrite(TransactionContextManager.createTransactionContext()).as(StepVerifier::create) @@ -124,11 +151,21 @@ public class ReactiveTransactionSupportTests { assertHasRolledBack(tm); assertHasNotSetRollbackOnly(tm); assertHasCleanedUp(tm); + + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.beforeRollbackCalled).isTrue(); + assertThat(tl.afterRollbackCalled).isTrue(); } @Test public void commitWithExistingTransaction() { ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(true, true); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::commit) .contextWrite(TransactionContextManager.createTransactionContext()) .as(StepVerifier::create).verifyComplete(); @@ -138,11 +175,21 @@ public class ReactiveTransactionSupportTests { assertHasNoRollback(tm); assertHasNotSetRollbackOnly(tm); assertHasNotCleanedUp(tm); + + assertThat(tl.beforeBeginCalled).isFalse(); + assertThat(tl.afterBeginCalled).isFalse(); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isFalse(); } @Test public void rollbackWithExistingTransaction() { ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(true, true); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::rollback) .contextWrite(TransactionContextManager.createTransactionContext()).as(StepVerifier::create) .verifyComplete(); @@ -152,11 +199,21 @@ public class ReactiveTransactionSupportTests { assertHasNoRollback(tm); assertHasSetRollbackOnly(tm); assertHasNotCleanedUp(tm); + + assertThat(tl.beforeBeginCalled).isFalse(); + assertThat(tl.afterBeginCalled).isFalse(); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isFalse(); } @Test public void rollbackOnlyWithExistingTransaction() { ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(true, true); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + tm.getReactiveTransaction(new DefaultTransactionDefinition()).doOnNext(ReactiveTransaction::setRollbackOnly).flatMap(tm::commit) .contextWrite(TransactionContextManager.createTransactionContext()).as(StepVerifier::create) .verifyComplete(); @@ -166,6 +223,13 @@ public class ReactiveTransactionSupportTests { assertHasNoRollback(tm); assertHasSetRollbackOnly(tm); assertHasNotCleanedUp(tm); + + assertThat(tl.beforeBeginCalled).isFalse(); + assertThat(tl.afterBeginCalled).isFalse(); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isFalse(); } @Test @@ -203,10 +267,11 @@ public class ReactiveTransactionSupportTests { assertHasCleanedUp(tm); } - //gh-28968 - @Test + @Test // gh-28968 void errorInCommitDoesInitiateRollbackAfterCommit() { - ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true, true); + ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, ConcurrencyFailureException::new, null); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); TransactionalOperator rxtx = TransactionalOperator.create(tm); StepVerifier.create(rxtx.transactional(Mono.just("bar"))) @@ -217,8 +282,79 @@ public class ReactiveTransactionSupportTests { assertHasRolledBack(tm); assertHasNotSetRollbackOnly(tm); assertHasCleanedUp(tm); + + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beforeCommitCalled).isTrue(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isTrue(); } + @Test + public void beginFailure() { + ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, false); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + + tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::commit) + .contextWrite(TransactionContextManager.createTransactionContext()) + .as(StepVerifier::create).verifyError(); + + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beginFailure).isInstanceOf(CannotCreateTransactionException.class); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.commitFailure).isNull(); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isFalse(); + assertThat(tl.rollbackFailure).isNull(); + } + + @Test + public void commitFailure() { + ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, TransactionSystemException::new, null); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + + tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::commit) + .contextWrite(TransactionContextManager.createTransactionContext()) + .as(StepVerifier::create).verifyError(); + + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beginFailure).isNull(); + assertThat(tl.beforeCommitCalled).isTrue(); + assertThat(tl.afterCommitCalled).isTrue(); + assertThat(tl.commitFailure).isInstanceOf(TransactionSystemException.class); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isFalse(); + assertThat(tl.rollbackFailure).isNull(); + } + + @Test + public void rollbackFailure() { + ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, null, TransactionSystemException::new); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + + tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::rollback) + .contextWrite(TransactionContextManager.createTransactionContext()) + .as(StepVerifier::create).verifyError(); + + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beginFailure).isNull(); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.commitFailure).isNull(); + assertThat(tl.beforeRollbackCalled).isTrue(); + assertThat(tl.afterRollbackCalled).isTrue(); + assertThat(tl.rollbackFailure).isInstanceOf(TransactionSystemException.class); + } + + private void assertHasBegan(ReactiveTestTransactionManager actual) { assertThat(actual.begin).as("Expected but was was not invoked").isTrue(); } diff --git a/spring-tx/src/test/java/org/springframework/transaction/support/TransactionSupportTests.java b/spring-tx/src/test/java/org/springframework/transaction/support/TransactionSupportTests.java index fb344e4573..7faa96c2d1 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/support/TransactionSupportTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/support/TransactionSupportTests.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test; import org.springframework.transaction.IllegalTransactionStateException; import org.springframework.transaction.MockCallbackPreferringTransactionManager; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TestTransactionExecutionListener; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionSystemException; @@ -63,6 +64,7 @@ class TransactionSupportTests { @Test void noExistingTransaction() { PlatformTransactionManager tm = new TestTransactionManager(false, true); + DefaultTransactionStatus status1 = (DefaultTransactionStatus) tm.getTransaction(new DefaultTransactionDefinition(PROPAGATION_SUPPORTS)); assertThat(status1.hasTransaction()).as("Must not have transaction").isFalse(); @@ -72,13 +74,14 @@ class TransactionSupportTests { assertThat(status2.hasTransaction()).as("Must have transaction").isTrue(); assertThat(status2.isNewTransaction()).as("Must be new transaction").isTrue(); - assertThatExceptionOfType(IllegalTransactionStateException.class).isThrownBy(() -> - tm.getTransaction(new DefaultTransactionDefinition(PROPAGATION_MANDATORY))); + assertThatExceptionOfType(IllegalTransactionStateException.class) + .isThrownBy(() -> tm.getTransaction(new DefaultTransactionDefinition(PROPAGATION_MANDATORY))); } @Test void existingTransaction() { PlatformTransactionManager tm = new TestTransactionManager(true, true); + DefaultTransactionStatus status1 = (DefaultTransactionStatus) tm.getTransaction(new DefaultTransactionDefinition(PROPAGATION_SUPPORTS)); assertThat(status1.getTransaction()).as("Must have transaction").isNotNull(); @@ -98,6 +101,9 @@ class TransactionSupportTests { @Test void commitWithoutExistingTransaction() { TestTransactionManager tm = new TestTransactionManager(false, true); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + TransactionStatus status = tm.getTransaction(null); tm.commit(status); @@ -105,11 +111,21 @@ class TransactionSupportTests { assertThat(tm.commit).as("triggered commit").isTrue(); assertThat(tm.rollback).as("no rollback").isFalse(); assertThat(tm.rollbackOnly).as("no rollbackOnly").isFalse(); + + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beforeCommitCalled).isTrue(); + assertThat(tl.afterCommitCalled).isTrue(); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isFalse(); } @Test void rollbackWithoutExistingTransaction() { TestTransactionManager tm = new TestTransactionManager(false, true); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + TransactionStatus status = tm.getTransaction(null); tm.rollback(status); @@ -117,11 +133,21 @@ class TransactionSupportTests { assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.rollback).as("triggered rollback").isTrue(); assertThat(tm.rollbackOnly).as("no rollbackOnly").isFalse(); + + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.beforeRollbackCalled).isTrue(); + assertThat(tl.afterRollbackCalled).isTrue(); } @Test void rollbackOnlyWithoutExistingTransaction() { TestTransactionManager tm = new TestTransactionManager(false, true); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + TransactionStatus status = tm.getTransaction(null); status.setRollbackOnly(); tm.commit(status); @@ -130,11 +156,21 @@ class TransactionSupportTests { assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.rollback).as("triggered rollback").isTrue(); assertThat(tm.rollbackOnly).as("no rollbackOnly").isFalse(); + + assertThat(tl.beforeBeginCalled).isTrue(); + assertThat(tl.afterBeginCalled).isTrue(); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.beforeRollbackCalled).isTrue(); + assertThat(tl.afterRollbackCalled).isTrue(); } @Test void commitWithExistingTransaction() { TestTransactionManager tm = new TestTransactionManager(true, true); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + TransactionStatus status = tm.getTransaction(null); tm.commit(status); @@ -142,11 +178,21 @@ class TransactionSupportTests { assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.rollback).as("no rollback").isFalse(); assertThat(tm.rollbackOnly).as("no rollbackOnly").isFalse(); + + assertThat(tl.beforeBeginCalled).isFalse(); + assertThat(tl.afterBeginCalled).isFalse(); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isFalse(); } @Test void rollbackWithExistingTransaction() { TestTransactionManager tm = new TestTransactionManager(true, true); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + TransactionStatus status = tm.getTransaction(null); tm.rollback(status); @@ -154,11 +200,21 @@ class TransactionSupportTests { assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.rollback).as("no rollback").isFalse(); assertThat(tm.rollbackOnly).as("triggered rollbackOnly").isTrue(); + + assertThat(tl.beforeBeginCalled).isFalse(); + assertThat(tl.afterBeginCalled).isFalse(); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isFalse(); } @Test void rollbackOnlyWithExistingTransaction() { TestTransactionManager tm = new TestTransactionManager(true, true); + TestTransactionExecutionListener tl = new TestTransactionExecutionListener(); + tm.addListener(tl); + TransactionStatus status = tm.getTransaction(null); status.setRollbackOnly(); tm.commit(status); @@ -167,6 +223,13 @@ class TransactionSupportTests { assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.rollback).as("no rollback").isFalse(); assertThat(tm.rollbackOnly).as("triggered rollbackOnly").isTrue(); + + assertThat(tl.beforeBeginCalled).isFalse(); + assertThat(tl.afterBeginCalled).isFalse(); + assertThat(tl.beforeCommitCalled).isFalse(); + assertThat(tl.afterCommitCalled).isFalse(); + assertThat(tl.beforeRollbackCalled).isFalse(); + assertThat(tl.afterRollbackCalled).isFalse(); } @Test @@ -204,14 +267,14 @@ class TransactionSupportTests { TestTransactionManager tm = new TestTransactionManager(false, true); TransactionTemplate template = new TransactionTemplate(tm); RuntimeException ex = new RuntimeException("Some application exception"); - assertThatRuntimeException().isThrownBy(() -> - template.execute(new TransactionCallbackWithoutResult() { + assertThatRuntimeException() + .isThrownBy(() -> template.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { throw ex; } })) - .isSameAs(ex); + .isSameAs(ex); assertThat(tm.begin).as("triggered begin").isTrue(); assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.rollback).as("triggered rollback").isTrue(); @@ -232,8 +295,8 @@ class TransactionSupportTests { TransactionTemplate template = new TransactionTemplate(tm); RuntimeException ex = new RuntimeException("Some application exception"); assertThatRuntimeException() - .isThrownBy(() -> template.executeWithoutResult(status -> { throw ex; })) - .isSameAs(tex); + .isThrownBy(() -> template.executeWithoutResult(status -> { throw ex; })) + .isSameAs(tex); assertThat(tm.begin).as("triggered begin").isTrue(); assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.rollback).as("triggered rollback").isTrue(); @@ -245,7 +308,7 @@ class TransactionSupportTests { TestTransactionManager tm = new TestTransactionManager(false, true); TransactionTemplate template = new TransactionTemplate(tm); assertThatExceptionOfType(Error.class) - .isThrownBy(() -> template.executeWithoutResult(status -> { throw new Error("Some application error"); })); + .isThrownBy(() -> template.executeWithoutResult(status -> { throw new Error("Some application error"); })); assertThat(tm.begin).as("triggered begin").isTrue(); assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.rollback).as("triggered rollback").isTrue(); @@ -265,12 +328,12 @@ class TransactionSupportTests { assertThat(template3).isEqualTo(template2); } + @Nested class AbstractPlatformTransactionManagerConfigurationTests { private final AbstractPlatformTransactionManager tm = new TestTransactionManager(false, true); - @Test void setTransactionSynchronizationNameToUnsupportedValues() { assertThatIllegalArgumentException().isThrownBy(() -> tm.setTransactionSynchronizationName(null)); @@ -302,22 +365,20 @@ class TransactionSupportTests { assertThat(tm.getTransactionSynchronization()).isEqualTo(SYNCHRONIZATION_ON_ACTUAL_TRANSACTION); } - private static Stream streamSynchronizationConstants() { return Arrays.stream(AbstractPlatformTransactionManager.class.getFields()) .filter(ReflectionUtils::isPublicStaticFinal) .map(Field::getName) .filter(name -> name.startsWith("SYNCHRONIZATION_")); } - } + @Nested class TransactionTemplateConfigurationTests { private final TransactionTemplate template = new TransactionTemplate(); - @Test void setTransactionManager() { TestTransactionManager tm = new TestTransactionManager(false, true); @@ -422,7 +483,6 @@ class TransactionSupportTests { .filter(ReflectionUtils::isPublicStaticFinal) .map(Field::getName); } - } }