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
This commit is contained in:
Juergen Hoeller
2023-08-06 14:03:44 +02:00
parent eaf54b54c3
commit 3253d2de89
22 changed files with 1372 additions and 481 deletions

View File

@@ -131,11 +131,6 @@ public abstract class JdbcTransactionObjectSupport implements SavepointManager,
return this.savepointAllowed; return this.savepointAllowed;
} }
@Override
public void flush() {
// no-op
}
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// Implementation of SavepointManager // Implementation of SavepointManager

View File

@@ -448,11 +448,6 @@ public class JmsTransactionManager extends AbstractPlatformTransactionManager
public boolean isRollbackOnly() { public boolean isRollbackOnly() {
return (this.resourceHolder != null && this.resourceHolder.isRollbackOnly()); return (this.resourceHolder != null && this.resourceHolder.isRollbackOnly());
} }
@Override
public void flush() {
// no-op
}
} }
} }

View File

@@ -88,8 +88,7 @@ class R2dbcTransactionManagerUnitTests {
ConnectionFactoryUtils.getConnection(connectionFactoryMock) ConnectionFactoryUtils.getConnection(connectionFactoryMock)
.flatMap(connection -> TransactionSynchronizationManager.forCurrentTransaction() .flatMap(connection -> TransactionSynchronizationManager.forCurrentTransaction()
.doOnNext(synchronizationManager -> synchronizationManager.registerSynchronization( .doOnNext(synchronizationManager -> synchronizationManager.registerSynchronization(sync)))
sync)))
.as(operator::transactional) .as(operator::transactional)
.as(StepVerifier::create) .as(StepVerifier::create)
.expectNextCount(1) .expectNextCount(1)
@@ -120,12 +119,11 @@ class R2dbcTransactionManagerUnitTests {
TransactionalOperator operator = TransactionalOperator.create(tm, definition); TransactionalOperator operator = TransactionalOperator.create(tm, definition);
ConnectionFactoryUtils.getConnection(connectionFactoryMock).as( ConnectionFactoryUtils.getConnection(connectionFactoryMock)
operator::transactional) .as(operator::transactional)
.as(StepVerifier::create) .as(StepVerifier::create)
.expectErrorSatisfies(actual -> assertThat(actual).isInstanceOf( .expectErrorSatisfies(actual -> assertThat(actual).isInstanceOf(
CannotCreateTransactionException.class).hasCauseInstanceOf( CannotCreateTransactionException.class).hasCauseInstanceOf(R2dbcBadGrammarException.class))
R2dbcBadGrammarException.class))
.verify(); .verify();
} }
@@ -140,12 +138,16 @@ class R2dbcTransactionManagerUnitTests {
definition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE); definition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
TransactionalOperator operator = TransactionalOperator.create(tm, definition); TransactionalOperator operator = TransactionalOperator.create(tm, definition);
operator.execute(tx -> {
ConnectionFactoryUtils.getConnection(connectionFactoryMock).as( assertThat(tx.getTransactionName()).isEqualTo("my-transaction");
operator::transactional) assertThat(tx.hasTransaction()).isTrue();
.as(StepVerifier::create) assertThat(tx.isNewTransaction()).isTrue();
.expectNextCount(1) assertThat(tx.isNested()).isFalse();
.verifyComplete(); assertThat(tx.isReadOnly()).isTrue();
assertThat(tx.isRollbackOnly()).isFalse();
assertThat(tx.isCompleted()).isFalse();
return Mono.empty();
}).as(StepVerifier::create).verifyComplete();
ArgumentCaptor<io.r2dbc.spi.TransactionDefinition> txCaptor = ArgumentCaptor.forClass(io.r2dbc.spi.TransactionDefinition.class); ArgumentCaptor<io.r2dbc.spi.TransactionDefinition> txCaptor = ArgumentCaptor.forClass(io.r2dbc.spi.TransactionDefinition.class);
verify(connectionMock).beginTransaction(txCaptor.capture()); verify(connectionMock).beginTransaction(txCaptor.capture());
@@ -171,8 +173,8 @@ class R2dbcTransactionManagerUnitTests {
TransactionalOperator operator = TransactionalOperator.create(tm, definition); TransactionalOperator operator = TransactionalOperator.create(tm, definition);
ConnectionFactoryUtils.getConnection(connectionFactoryMock).as( ConnectionFactoryUtils.getConnection(connectionFactoryMock)
operator::transactional) .as(operator::transactional)
.as(StepVerifier::create) .as(StepVerifier::create)
.expectNextCount(1) .expectNextCount(1)
.verifyComplete(); .verifyComplete();
@@ -190,8 +192,8 @@ class R2dbcTransactionManagerUnitTests {
TransactionalOperator operator = TransactionalOperator.create(tm, definition); TransactionalOperator operator = TransactionalOperator.create(tm, definition);
ConnectionFactoryUtils.getConnection(connectionFactoryMock).as( ConnectionFactoryUtils.getConnection(connectionFactoryMock)
operator::transactional) .as(operator::transactional)
.as(StepVerifier::create) .as(StepVerifier::create)
.expectNextCount(1) .expectNextCount(1)
.verifyComplete(); .verifyComplete();
@@ -215,8 +217,8 @@ class R2dbcTransactionManagerUnitTests {
TransactionalOperator operator = TransactionalOperator.create(tm, definition); TransactionalOperator operator = TransactionalOperator.create(tm, definition);
ConnectionFactoryUtils.getConnection(connectionFactoryMock).as( ConnectionFactoryUtils.getConnection(connectionFactoryMock)
operator::transactional) .as(operator::transactional)
.as(StepVerifier::create) .as(StepVerifier::create)
.expectNextCount(1) .expectNextCount(1)
.verifyComplete(); .verifyComplete();
@@ -262,11 +264,9 @@ class R2dbcTransactionManagerUnitTests {
TransactionalOperator operator = TransactionalOperator.create(tm); TransactionalOperator operator = TransactionalOperator.create(tm);
ConnectionFactoryUtils.getConnection(connectionFactoryMock) ConnectionFactoryUtils.getConnection(connectionFactoryMock)
.doOnNext(connection -> { .doOnNext(connection -> { throw new IllegalStateException(); })
throw new IllegalStateException(); .as(operator::transactional)
}).as(operator::transactional) .as(StepVerifier::create).verifyError(IllegalStateException.class);
.as(StepVerifier::create)
.verifyError(IllegalStateException.class);
assertThat(commits).hasValue(0); assertThat(commits).hasValue(0);
assertThat(rollbacks).hasValue(1); assertThat(rollbacks).hasValue(1);
@@ -286,8 +286,7 @@ class R2dbcTransactionManagerUnitTests {
reactiveTransaction.setRollbackOnly(); reactiveTransaction.setRollbackOnly();
return ConnectionFactoryUtils.getConnection(connectionFactoryMock) return ConnectionFactoryUtils.getConnection(connectionFactoryMock)
.doOnNext(connection -> connection.createStatement("foo")).then(); .doOnNext(connection -> connection.createStatement("foo")).then();
}).as(StepVerifier::create) }).as(StepVerifier::create).verifyError(BadSqlGrammarException.class);
.verifyError(BadSqlGrammarException.class);
verify(connectionMock).beginTransaction(any(io.r2dbc.spi.TransactionDefinition.class)); verify(connectionMock).beginTransaction(any(io.r2dbc.spi.TransactionDefinition.class));
verify(connectionMock).createStatement("foo"); verify(connectionMock).createStatement("foo");
@@ -308,7 +307,7 @@ class R2dbcTransactionManagerUnitTests {
.doOnNext(connection -> { .doOnNext(connection -> {
throw new IllegalStateException("Intentional error to trigger rollback"); throw new IllegalStateException("Intentional error to trigger rollback");
}).then()).as(StepVerifier::create) }).then()).as(StepVerifier::create)
.verifyErrorSatisfies(e -> assertThat(e) .verifyErrorSatisfies(ex -> assertThat(ex)
.isInstanceOf(BadSqlGrammarException.class) .isInstanceOf(BadSqlGrammarException.class)
.hasCause(new R2dbcBadGrammarException("Rollback should fail")) .hasCause(new R2dbcBadGrammarException("Rollback should fail"))
); );
@@ -327,15 +326,21 @@ class R2dbcTransactionManagerUnitTests {
TransactionalOperator operator = TransactionalOperator.create(tm); TransactionalOperator operator = TransactionalOperator.create(tm);
operator.execute(tx -> { operator.execute(tx -> {
tx.setRollbackOnly(); assertThat(tx.getTransactionName()).isEmpty();
assertThat(tx.hasTransaction()).isTrue();
assertThat(tx.isNewTransaction()).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( return TransactionSynchronizationManager.forCurrentTransaction().doOnNext(
synchronizationManager -> { synchronizationManager -> {
assertThat(synchronizationManager.hasResource(connectionFactoryMock)).isTrue(); assertThat(synchronizationManager.hasResource(connectionFactoryMock)).isTrue();
synchronizationManager.registerSynchronization(sync); synchronizationManager.registerSynchronization(sync);
}).then(); }).then();
}).as(StepVerifier::create) }).as(StepVerifier::create).verifyComplete();
.verifyComplete();
verify(connectionMock).beginTransaction(any(io.r2dbc.spi.TransactionDefinition.class)); verify(connectionMock).beginTransaction(any(io.r2dbc.spi.TransactionDefinition.class));
verify(connectionMock).rollbackTransaction(); verify(connectionMock).rollbackTransaction();
@@ -357,14 +362,19 @@ class R2dbcTransactionManagerUnitTests {
TransactionalOperator operator = TransactionalOperator.create(tm, definition); TransactionalOperator operator = TransactionalOperator.create(tm, definition);
operator.execute(tx1 -> { operator.execute(tx1 -> {
assertThat(tx1.getTransactionName()).isEmpty();
assertThat(tx1.hasTransaction()).isTrue();
assertThat(tx1.isNewTransaction()).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); definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER);
return operator.execute(tx2 -> { return operator.execute(tx2 -> {
fail("Should have thrown IllegalTransactionStateException"); fail("Should have thrown IllegalTransactionStateException");
return Mono.empty(); return Mono.empty();
}); });
}).as(StepVerifier::create) }).as(StepVerifier::create).verifyError(IllegalTransactionStateException.class);
.verifyError(IllegalTransactionStateException.class);
verify(connectionMock).rollbackTransaction(); verify(connectionMock).rollbackTransaction();
verify(connectionMock).close(); verify(connectionMock).close();
@@ -381,14 +391,17 @@ class R2dbcTransactionManagerUnitTests {
TransactionalOperator operator = TransactionalOperator.create(tm, definition); TransactionalOperator operator = TransactionalOperator.create(tm, definition);
operator.execute(tx1 -> { operator.execute(tx1 -> {
assertThat(tx1.isNewTransaction()).isTrue(); assertThat(tx1.hasTransaction()).isTrue();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); assertThat(tx1.isNewTransaction()).isTrue();
return operator.execute(tx2 -> { assertThat(tx1.isNested()).isFalse();
assertThat(tx2.isNewTransaction()).isTrue(); definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
return Mono.empty(); return operator.execute(tx2 -> {
}); assertThat(tx2.hasTransaction()).isTrue();
}).as(StepVerifier::create) assertThat(tx2.isNewTransaction()).isTrue();
.verifyComplete(); assertThat(tx2.isNested()).isTrue();
return Mono.empty();
});
}).as(StepVerifier::create).verifyComplete();
verify(connectionMock).createSavepoint("SAVEPOINT_1"); verify(connectionMock).createSavepoint("SAVEPOINT_1");
verify(connectionMock).releaseSavepoint("SAVEPOINT_1"); verify(connectionMock).releaseSavepoint("SAVEPOINT_1");
@@ -407,15 +420,20 @@ class R2dbcTransactionManagerUnitTests {
TransactionalOperator operator = TransactionalOperator.create(tm, definition); TransactionalOperator operator = TransactionalOperator.create(tm, definition);
operator.execute(tx1 -> { operator.execute(tx1 -> {
assertThat(tx1.isNewTransaction()).isTrue(); assertThat(tx1.hasTransaction()).isTrue();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); assertThat(tx1.isNewTransaction()).isTrue();
return operator.execute(tx2 -> { assertThat(tx1.isNested()).isFalse();
assertThat(tx2.isNewTransaction()).isTrue(); definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
tx2.setRollbackOnly(); return operator.execute(tx2 -> {
return Mono.empty(); assertThat(tx2.hasTransaction()).isTrue();
}); assertThat(tx2.isNewTransaction()).isTrue();
}).as(StepVerifier::create) assertThat(tx2.isNested()).isTrue();
.verifyComplete(); assertThat(tx2.isRollbackOnly()).isFalse();
tx2.setRollbackOnly();
assertThat(tx2.isRollbackOnly()).isTrue();
return Mono.empty();
});
}).as(StepVerifier::create).verifyComplete();
verify(connectionMock).createSavepoint("SAVEPOINT_1"); verify(connectionMock).createSavepoint("SAVEPOINT_1");
verify(connectionMock).rollbackTransactionToSavepoint("SAVEPOINT_1"); verify(connectionMock).rollbackTransactionToSavepoint("SAVEPOINT_1");
@@ -432,16 +450,19 @@ class R2dbcTransactionManagerUnitTests {
TransactionalOperator operator = TransactionalOperator.create(tm, definition); TransactionalOperator operator = TransactionalOperator.create(tm, definition);
operator.execute(tx1 -> { operator.execute(tx1 -> {
assertThat(tx1.isNewTransaction()).isFalse(); assertThat(tx1.hasTransaction()).isFalse();
DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition(); assertThat(tx1.isNewTransaction()).isFalse();
innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); assertThat(tx1.isNested()).isFalse();
TransactionalOperator inner = TransactionalOperator.create(tm, innerDef); DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition();
return inner.execute(tx2 -> { innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
assertThat(tx2.isNewTransaction()).isTrue(); TransactionalOperator inner = TransactionalOperator.create(tm, innerDef);
return Mono.empty(); return inner.execute(tx2 -> {
}); assertThat(tx2.hasTransaction()).isTrue();
}).as(StepVerifier::create) assertThat(tx2.isNewTransaction()).isTrue();
.verifyComplete(); assertThat(tx2.isNested()).isFalse();
return Mono.empty();
});
}).as(StepVerifier::create).verifyComplete();
verify(connectionMock).commitTransaction(); verify(connectionMock).commitTransaction();
verify(connectionMock).close(); verify(connectionMock).close();
@@ -456,17 +477,22 @@ class R2dbcTransactionManagerUnitTests {
TransactionalOperator operator = TransactionalOperator.create(tm, definition); TransactionalOperator operator = TransactionalOperator.create(tm, definition);
operator.execute(tx1 -> { operator.execute(tx1 -> {
assertThat(tx1.isNewTransaction()).isFalse(); assertThat(tx1.hasTransaction()).isFalse();
DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition(); assertThat(tx1.isNewTransaction()).isFalse();
innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); assertThat(tx1.isNested()).isFalse();
TransactionalOperator inner = TransactionalOperator.create(tm, innerDef); DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition();
return inner.execute(tx2 -> { innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
assertThat(tx2.isNewTransaction()).isTrue(); TransactionalOperator inner = TransactionalOperator.create(tm, innerDef);
tx2.setRollbackOnly(); return inner.execute(tx2 -> {
return Mono.empty(); assertThat(tx2.hasTransaction()).isTrue();
}); assertThat(tx2.isNewTransaction()).isTrue();
}).as(StepVerifier::create) assertThat(tx2.isNested()).isFalse();
.verifyComplete(); assertThat(tx2.isRollbackOnly()).isFalse();
tx2.setRollbackOnly();
assertThat(tx2.isRollbackOnly()).isTrue();
return Mono.empty();
});
}).as(StepVerifier::create).verifyComplete();
verify(connectionMock).rollbackTransaction(); verify(connectionMock).rollbackTransaction();
verify(connectionMock).close(); verify(connectionMock).close();
@@ -481,16 +507,19 @@ class R2dbcTransactionManagerUnitTests {
TransactionalOperator operator = TransactionalOperator.create(tm, definition); TransactionalOperator operator = TransactionalOperator.create(tm, definition);
operator.execute(tx1 -> { operator.execute(tx1 -> {
assertThat(tx1.hasTransaction()).isFalse();
assertThat(tx1.isNewTransaction()).isFalse(); assertThat(tx1.isNewTransaction()).isFalse();
assertThat(tx1.isNested()).isFalse();
DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition(); DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition();
innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionalOperator inner = TransactionalOperator.create(tm, innerDef); TransactionalOperator inner = TransactionalOperator.create(tm, innerDef);
return inner.execute(tx2 -> { return inner.execute(tx2 -> {
assertThat(tx2.hasTransaction()).isTrue();
assertThat(tx2.isNewTransaction()).isTrue(); assertThat(tx2.isNewTransaction()).isTrue();
assertThat(tx2.isNested()).isFalse();
return Mono.empty(); return Mono.empty();
}); });
}).as(StepVerifier::create) }).as(StepVerifier::create).verifyComplete();
.verifyComplete();
verify(connectionMock).commitTransaction(); verify(connectionMock).commitTransaction();
verify(connectionMock).close(); verify(connectionMock).close();
@@ -505,17 +534,22 @@ class R2dbcTransactionManagerUnitTests {
TransactionalOperator operator = TransactionalOperator.create(tm, definition); TransactionalOperator operator = TransactionalOperator.create(tm, definition);
operator.execute(tx1 -> { operator.execute(tx1 -> {
assertThat(tx1.isNewTransaction()).isFalse(); assertThat(tx1.hasTransaction()).isFalse();
DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition(); assertThat(tx1.isNewTransaction()).isFalse();
innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); assertThat(tx1.isNested()).isFalse();
TransactionalOperator inner = TransactionalOperator.create(tm, innerDef); DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition();
return inner.execute(tx2 -> { innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
assertThat(tx2.isNewTransaction()).isTrue(); TransactionalOperator inner = TransactionalOperator.create(tm, innerDef);
tx2.setRollbackOnly(); return inner.execute(tx2 -> {
return Mono.empty(); assertThat(tx2.hasTransaction()).isTrue();
}); assertThat(tx2.isNewTransaction()).isTrue();
}).as(StepVerifier::create) assertThat(tx2.isNested()).isFalse();
.verifyComplete(); assertThat(tx2.isRollbackOnly()).isFalse();
tx2.setRollbackOnly();
assertThat(tx2.isRollbackOnly()).isTrue();
return Mono.empty();
});
}).as(StepVerifier::create).verifyComplete();
verify(connectionMock).rollbackTransaction(); verify(connectionMock).rollbackTransaction();
verify(connectionMock).close(); verify(connectionMock).close();

View File

@@ -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<TransactionExecutionListener> listeners);
/**
* Return the registered transaction execution listeners for this transaction manager.
* @see #setTransactionExecutionListeners
*/
Collection<TransactionExecutionListener> 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);
}
}

View File

@@ -42,6 +42,7 @@ import org.springframework.lang.Nullable;
* @see org.springframework.transaction.support.TransactionTemplate * @see org.springframework.transaction.support.TransactionTemplate
* @see org.springframework.transaction.interceptor.TransactionInterceptor * @see org.springframework.transaction.interceptor.TransactionInterceptor
* @see org.springframework.transaction.ReactiveTransactionManager * @see org.springframework.transaction.ReactiveTransactionManager
* @see ConfigurableTransactionManager
*/ */
public interface PlatformTransactionManager extends TransactionManager { public interface PlatformTransactionManager extends TransactionManager {
@@ -68,8 +69,7 @@ public interface PlatformTransactionManager extends TransactionManager {
* @see TransactionDefinition#getTimeout * @see TransactionDefinition#getTimeout
* @see TransactionDefinition#isReadOnly * @see TransactionDefinition#isReadOnly
*/ */
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
throws TransactionException;
/** /**
* Commit the given transaction, with regard to its status. If the transaction * Commit the given transaction, with regard to its status. If the transaction

View File

@@ -17,7 +17,7 @@
package org.springframework.transaction; 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} * This is currently a marker interface extending {@link TransactionExecution}
* but may acquire further methods in a future revision. * but may acquire further methods in a future revision.
* *

View File

@@ -32,6 +32,7 @@ import org.springframework.lang.Nullable;
* @see org.springframework.transaction.reactive.TransactionalOperator * @see org.springframework.transaction.reactive.TransactionalOperator
* @see org.springframework.transaction.interceptor.TransactionInterceptor * @see org.springframework.transaction.interceptor.TransactionInterceptor
* @see org.springframework.transaction.PlatformTransactionManager * @see org.springframework.transaction.PlatformTransactionManager
* @see ConfigurableTransactionManager
*/ */
public interface ReactiveTransactionManager extends TransactionManager { public interface ReactiveTransactionManager extends TransactionManager {

View File

@@ -19,7 +19,8 @@ package org.springframework.transaction;
/** /**
* Common representation of the current state of a transaction. * Common representation of the current state of a transaction.
* Serves as base interface for {@link TransactionStatus} as well as * 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 * @author Juergen Hoeller
* @since 5.2 * @since 5.2
@@ -27,29 +28,102 @@ package org.springframework.transaction;
public interface TransactionExecution { public interface TransactionExecution {
/** /**
* Return whether the present transaction is new; otherwise participating * Return the defined name of the transaction (possibly an empty String).
* in an existing transaction, or potentially not running in an actual * <p>In case of Spring's declarative transactions, the exposed name will be
* transaction in the first place. * the {@code fully-qualified class name + "." + method name} (by default).
* <p>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.
* <p>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.
* <p>This is primarily here for transaction manager state handling.
* Prefer the use of {@link #hasTransaction()} for application purposes
* since this is usually semantically appropriate.
* <p>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()}.
* <p>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.
* <p>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.
* <p>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 * Set the transaction rollback-only. This instructs the transaction manager
* that the only possible outcome of the transaction may be a rollback, as * 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. * alternative to throwing an exception which would in turn trigger a rollback.
* <p>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 * Return whether the transaction has been marked as rollback-only
* (either by the application or by the transaction infrastructure). * (either by the application or by the transaction infrastructure).
* <p>The default implementation returns {@code false}.
* @see #setRollbackOnly()
*/ */
boolean isRollbackOnly(); default boolean isRollbackOnly() {
return false;
}
/** /**
* Return whether this transaction is completed, that is, * Return whether this transaction is completed, that is,
* whether it has already been committed or rolled back. * whether it has already been committed or rolled back.
* <p>The default implementation returns {@code false}.
*/ */
boolean isCompleted(); default boolean isCompleted() {
return false;
}
} }

View File

@@ -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.
*
* <p>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) {
}
}

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; import java.io.Flushable;
/** /**
* Representation of the status of a transaction. * Representation of an ongoing {@link PlatformTransactionManager} transaction.
* Extends the common {@link TransactionExecution} interface.
* *
* <p>Transactional code can use this to retrieve status information, * <p>Transactional code can use this to retrieve status information,
* and to programmatically request a rollback (instead of throwing * and to programmatically request a rollback (instead of throwing
@@ -44,12 +45,15 @@ public interface TransactionStatus extends TransactionExecution, SavepointManage
* <p>This method is mainly here for diagnostic purposes, alongside * <p>This method is mainly here for diagnostic purposes, alongside
* {@link #isNewTransaction()}. For programmatic handling of custom * {@link #isNewTransaction()}. For programmatic handling of custom
* savepoints, use the operations provided by {@link SavepointManager}. * savepoints, use the operations provided by {@link SavepointManager}.
* <p>The default implementation returns {@code false}.
* @see #isNewTransaction() * @see #isNewTransaction()
* @see #createSavepoint() * @see #createSavepoint()
* @see #rollbackToSavepoint(Object) * @see #rollbackToSavepoint(Object)
* @see #releaseSavepoint(Object) * @see #releaseSavepoint(Object)
*/ */
boolean hasSavepoint(); default boolean hasSavepoint() {
return false;
}
/** /**
* Flush the underlying session to the datastore, if applicable: * 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 * transaction manager does not have a flush concept. A flush signal may
* get applied to the primary resource or to transaction synchronizations, * get applied to the primary resource or to transaction synchronizations,
* depending on the underlying resource. * depending on the underlying resource.
* <p>The default implementation is empty, considering flush as a no-op.
*/ */
@Override @Override
void flush(); default void flush() {
}
} }

View File

@@ -19,6 +19,8 @@ package org.springframework.transaction.reactive;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -30,12 +32,14 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.transaction.ConfigurableTransactionManager;
import org.springframework.transaction.IllegalTransactionStateException; import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.InvalidTimeoutException; import org.springframework.transaction.InvalidTimeoutException;
import org.springframework.transaction.ReactiveTransaction; import org.springframework.transaction.ReactiveTransaction;
import org.springframework.transaction.ReactiveTransactionManager; import org.springframework.transaction.ReactiveTransactionManager;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionExecutionListener;
import org.springframework.transaction.TransactionSuspensionNotSupportedException; import org.springframework.transaction.TransactionSuspensionNotSupportedException;
import org.springframework.transaction.UnexpectedRollbackException; import org.springframework.transaction.UnexpectedRollbackException;
@@ -77,10 +81,24 @@ import org.springframework.transaction.UnexpectedRollbackException;
* @see TransactionSynchronizationManager * @see TransactionSynchronizationManager
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public abstract class AbstractReactiveTransactionManager implements ReactiveTransactionManager, Serializable { public abstract class AbstractReactiveTransactionManager
implements ReactiveTransactionManager, ConfigurableTransactionManager, Serializable {
protected transient Log logger = LogFactory.getLog(getClass()); protected transient Log logger = LogFactory.getLog(getClass());
private Collection<TransactionExecutionListener> transactionExecutionListeners = new ArrayList<>();
@Override
public final void setTransactionExecutionListeners(Collection<TransactionExecutionListener> listeners) {
this.transactionExecutionListeners = listeners;
}
@Override
public final Collection<TransactionExecutionListener> getTransactionExecutionListeners() {
return this.transactionExecutionListeners;
}
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// Implementation of ReactiveTransactionManager // Implementation of ReactiveTransactionManager
@@ -99,8 +117,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
// Use defaults if no transaction definition given. // Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults()); TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
return TransactionSynchronizationManager.forCurrentTransaction() return TransactionSynchronizationManager.forCurrentTransaction().flatMap(synchronizationManager -> {
.flatMap(synchronizationManager -> {
Object transaction = doGetTransaction(synchronizationManager); Object transaction = doGetTransaction(synchronizationManager);
@@ -139,13 +156,16 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
return Mono.defer(() -> { return Mono.defer(() -> {
GenericReactiveTransaction status = newReactiveTransaction( GenericReactiveTransaction status = newReactiveTransaction(
nestedSynchronizationManager, def, transaction, true, 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) return doBegin(nestedSynchronizationManager, transaction, def)
.doOnSuccess(ignore -> prepareSynchronization(nestedSynchronizationManager, status, def)) .doOnSuccess(ignore -> prepareSynchronization(nestedSynchronizationManager, status, def))
.doOnError(ex -> this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, ex)))
.thenReturn(status); .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)) ex -> resume(nestedSynchronizationManager, null, suspendedResources.orElse(null))
.then(Mono.error(ex))); .then(Mono.error(ex)));
})); }));
} }
else { else {
@@ -190,9 +210,13 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
Mono<SuspendedResourcesHolder> suspendedResources = suspend(synchronizationManager, transaction); Mono<SuspendedResourcesHolder> suspendedResources = suspend(synchronizationManager, transaction);
return suspendedResources.flatMap(suspendedResourcesHolder -> { return suspendedResources.flatMap(suspendedResourcesHolder -> {
GenericReactiveTransaction status = newReactiveTransaction(synchronizationManager, GenericReactiveTransaction status = newReactiveTransaction(synchronizationManager,
definition, transaction, true, debugEnabled, suspendedResourcesHolder); definition, transaction, true, false, debugEnabled, suspendedResourcesHolder);
return doBegin(synchronizationManager, transaction, definition).doOnSuccess(ignore -> this.transactionExecutionListeners.forEach(listener -> listener.beforeBegin(status));
prepareSynchronization(synchronizationManager, status, definition)).thenReturn(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 -> .onErrorResume(ErrorPredicates.RUNTIME_OR_ERROR, beginEx ->
resumeAfterBeginException(synchronizationManager, transaction, suspendedResourcesHolder, beginEx) resumeAfterBeginException(synchronizationManager, transaction, suspendedResourcesHolder, beginEx)
.then(Mono.error(beginEx))); .then(Mono.error(beginEx)));
@@ -205,7 +229,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
} }
// Nested transaction through nested begin and commit/rollback calls. // Nested transaction through nested begin and commit/rollback calls.
GenericReactiveTransaction status = newReactiveTransaction(synchronizationManager, GenericReactiveTransaction status = newReactiveTransaction(synchronizationManager,
definition, transaction, true, debugEnabled, null); definition, transaction, true, true, debugEnabled, null);
return doBegin(synchronizationManager, transaction, definition).doOnSuccess(ignore -> return doBegin(synchronizationManager, transaction, definition).doOnSuccess(ignore ->
prepareSynchronization(synchronizationManager, status, definition)).thenReturn(status); 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) { @Nullable Object transaction, boolean newTransaction, boolean debug, @Nullable Object suspendedResources) {
GenericReactiveTransaction status = newReactiveTransaction(synchronizationManager, GenericReactiveTransaction status = newReactiveTransaction(synchronizationManager,
definition, transaction, newTransaction, debug, suspendedResources); definition, transaction, newTransaction, false, debug, suspendedResources);
prepareSynchronization(synchronizationManager, status, definition); prepareSynchronization(synchronizationManager, status, definition);
return status; return status;
} }
@@ -238,11 +262,12 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
*/ */
private GenericReactiveTransaction newReactiveTransaction( private GenericReactiveTransaction newReactiveTransaction(
TransactionSynchronizationManager synchronizationManager, TransactionDefinition definition, 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, return new GenericReactiveTransaction(definition.getName(), transaction,
!synchronizationManager.isSynchronizationActive(), newTransaction, !synchronizationManager.isSynchronizationActive(),
definition.isReadOnly(), debug, suspendedResources); nested, definition.isReadOnly(), debug, suspendedResources);
} }
/** /**
@@ -438,6 +463,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
if (status.isDebug()) { if (status.isDebug()) {
logger.debug("Initiating transaction commit"); logger.debug("Initiating transaction commit");
} }
this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status));
return doCommit(synchronizationManager, status); return doCommit(synchronizationManager, status);
} }
return Mono.empty(); return Mono.empty();
@@ -449,11 +475,21 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
Mono<Void> result = propagateException; Mono<Void> result = propagateException;
if (ErrorPredicates.UNEXPECTED_ROLLBACK.test(ex)) { if (ErrorPredicates.UNEXPECTED_ROLLBACK.test(ex)) {
result = triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_ROLLED_BACK) 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)) { else if (ErrorPredicates.TRANSACTION_EXCEPTION.test(ex)) {
result = triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_UNKNOWN) 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)) { else if (ErrorPredicates.RUNTIME_OR_ERROR.test(ex)) {
Mono<Void> mono; Mono<Void> mono;
@@ -471,7 +507,13 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
}) })
.then(Mono.defer(() -> triggerAfterCommit(synchronizationManager, status).onErrorResume(ex -> .then(Mono.defer(() -> triggerAfterCommit(synchronizationManager, status).onErrorResume(ex ->
triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_COMMITTED).then(Mono.error(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 return commit
.onErrorResume(ex -> cleanupAfterCompletion(synchronizationManager, status) .onErrorResume(ex -> cleanupAfterCompletion(synchronizationManager, status)
@@ -510,6 +552,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
if (status.isDebug()) { if (status.isDebug()) {
logger.debug("Initiating transaction rollback"); logger.debug("Initiating transaction rollback");
} }
this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status));
return doRollback(synchronizationManager, status); return doRollback(synchronizationManager, status);
} }
else { else {
@@ -528,10 +571,22 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
} }
})).onErrorResume(ErrorPredicates.RUNTIME_OR_ERROR, ex -> triggerAfterCompletion( })).onErrorResume(ErrorPredicates.RUNTIME_OR_ERROR, ex -> triggerAfterCompletion(
synchronizationManager, status, TransactionSynchronization.STATUS_UNKNOWN) 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(() -> 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))) .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 -> { }).onErrorResume(ErrorPredicates.RUNTIME_OR_ERROR, rbex -> {
logger.error("Commit exception overridden by rollback exception", ex); logger.error("Commit exception overridden by rollback exception", ex);
return triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_UNKNOWN) return triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_UNKNOWN)
.then(Mono.error(rbex)); .then(Mono.defer(() -> {
}).then(triggerAfterCompletion(synchronizationManager, status, TransactionSynchronization.STATUS_ROLLED_BACK)); 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();
}));
} }

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 { public class GenericReactiveTransaction implements ReactiveTransaction {
@Nullable
private final String transactionName;
@Nullable @Nullable
private final Object transaction; private final Object transaction;
@@ -47,6 +50,8 @@ public class GenericReactiveTransaction implements ReactiveTransaction {
private final boolean newSynchronization; private final boolean newSynchronization;
private final boolean nested;
private final boolean readOnly; private final boolean readOnly;
private final boolean debug; private final boolean debug;
@@ -61,6 +66,7 @@ public class GenericReactiveTransaction implements ReactiveTransaction {
/** /**
* Create a new {@code DefaultReactiveTransactionStatus} instance. * Create a new {@code DefaultReactiveTransactionStatus} instance.
* @param transactionName the defined name of the transaction
* @param transaction underlying transaction object that can hold state * @param transaction underlying transaction object that can hold state
* for the internal transaction implementation * for the internal transaction implementation
* @param newTransaction if the transaction is new, otherwise participating * @param newTransaction if the transaction is new, otherwise participating
@@ -73,19 +79,35 @@ public class GenericReactiveTransaction implements ReactiveTransaction {
* debug logging should be enabled. * debug logging should be enabled.
* @param suspendedResources a holder for resources that have been suspended * @param suspendedResources a holder for resources that have been suspended
* for this transaction, if any * for this transaction, if any
* @since 6.1
*/ */
public GenericReactiveTransaction( public GenericReactiveTransaction(
@Nullable Object transaction, boolean newTransaction, boolean newSynchronization, @Nullable String transactionName, @Nullable Object transaction, boolean newTransaction,
boolean readOnly, boolean debug, @Nullable Object suspendedResources) { boolean newSynchronization, boolean nested, boolean readOnly, boolean debug,
@Nullable Object suspendedResources) {
this.transactionName = transactionName;
this.transaction = transaction; this.transaction = transaction;
this.newTransaction = newTransaction; this.newTransaction = newTransaction;
this.newSynchronization = newSynchronization; this.newSynchronization = newSynchronization;
this.nested = nested;
this.readOnly = readOnly; this.readOnly = readOnly;
this.debug = debug; this.debug = debug;
this.suspendedResources = suspendedResources; 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. * Return the underlying transaction object.
@@ -96,9 +118,7 @@ public class GenericReactiveTransaction implements ReactiveTransaction {
return this.transaction; return this.transaction;
} }
/** @Override
* Return whether there is an actual transaction active.
*/
public boolean hasTransaction() { public boolean hasTransaction() {
return (this.transaction != null); return (this.transaction != null);
} }
@@ -109,16 +129,18 @@ public class GenericReactiveTransaction implements ReactiveTransaction {
} }
/** /**
* Return if a new transaction synchronization has been opened * Return if a new transaction synchronization has been opened for this transaction.
* for this transaction.
*/ */
public boolean isNewSynchronization() { public boolean isNewSynchronization() {
return this.newSynchronization; return this.newSynchronization;
} }
/** @Override
* Return if this transaction is defined as read-only transaction. public boolean isNested() {
*/ return this.nested;
}
@Override
public boolean isReadOnly() { public boolean isReadOnly() {
return this.readOnly; return this.readOnly;
} }
@@ -143,6 +165,9 @@ public class GenericReactiveTransaction implements ReactiveTransaction {
@Override @Override
public void setRollbackOnly() { public void setRollbackOnly() {
if (this.completed) {
throw new IllegalStateException("Transaction completed");
}
this.rollbackOnly = true; this.rollbackOnly = true;
} }

View File

@@ -19,6 +19,8 @@ package org.springframework.transaction.support;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -26,12 +28,14 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.transaction.ConfigurableTransactionManager;
import org.springframework.transaction.IllegalTransactionStateException; import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.InvalidTimeoutException; import org.springframework.transaction.InvalidTimeoutException;
import org.springframework.transaction.NestedTransactionNotSupportedException; import org.springframework.transaction.NestedTransactionNotSupportedException;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionExecutionListener;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSuspensionNotSupportedException; import org.springframework.transaction.TransactionSuspensionNotSupportedException;
import org.springframework.transaction.UnexpectedRollbackException; import org.springframework.transaction.UnexpectedRollbackException;
@@ -82,7 +86,8 @@ import org.springframework.util.Assert;
* @see org.springframework.transaction.jta.JtaTransactionManager * @see org.springframework.transaction.jta.JtaTransactionManager
*/ */
@SuppressWarnings("serial") @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 * Always activate transaction synchronization, even for "empty" transactions
@@ -136,6 +141,8 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
private boolean rollbackOnCommitFailure = false; private boolean rollbackOnCommitFailure = false;
private Collection<TransactionExecutionListener> transactionExecutionListeners = new ArrayList<>();
/** /**
* Set the transaction synchronization by the name of the corresponding constant * Set the transaction synchronization by the name of the corresponding constant
@@ -339,6 +346,16 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
return this.rollbackOnCommitFailure; return this.rollbackOnCommitFailure;
} }
@Override
public final void setTransactionExecutionListeners(Collection<TransactionExecutionListener> listeners) {
this.transactionExecutionListeners = listeners;
}
@Override
public final Collection<TransactionExecutionListener> getTransactionExecutionListeners() {
return this.transactionExecutionListeners;
}
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// Implementation of PlatformTransactionManager // Implementation of PlatformTransactionManager
@@ -385,7 +402,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def); logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
} }
try { try {
return startTransaction(def, transaction, debugEnabled, suspendedResources); return startTransaction(def, transaction, false, debugEnabled, suspendedResources);
} }
catch (RuntimeException | Error ex) { catch (RuntimeException | Error ex) {
resume(null, suspendedResources); 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. * Create a TransactionStatus for an existing transaction.
*/ */
@@ -446,7 +449,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
} }
SuspendedResourcesHolder suspendedResources = suspend(transaction); SuspendedResourcesHolder suspendedResources = suspend(transaction);
try { try {
return startTransaction(definition, transaction, debugEnabled, suspendedResources); return startTransaction(definition, transaction, false, debugEnabled, suspendedResources);
} }
catch (RuntimeException | Error beginEx) { catch (RuntimeException | Error beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx); resumeAfterBeginException(transaction, suspendedResources, beginEx);
@@ -467,16 +470,24 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
// Create savepoint within existing Spring-managed transaction, // Create savepoint within existing Spring-managed transaction,
// through the SavepointManager API implemented by TransactionStatus. // through the SavepointManager API implemented by TransactionStatus.
// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization. // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
DefaultTransactionStatus status = DefaultTransactionStatus status = newTransactionStatus(
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null); definition, transaction, false, false, true, debugEnabled, null);
status.createAndHoldSavepoint(); 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; return status;
} }
else { else {
// Nested transaction through nested begin and commit/rollback calls. // Nested transaction through nested begin and commit/rollback calls.
// Usually only for JTA: Spring synchronization might get activated here // Usually only for JTA: Spring synchronization might get activated here
// in case of a pre-existing JTA transaction. // 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); 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, * Create a new TransactionStatus for the given arguments,
* also initializing transaction synchronization as appropriate. * also initializing transaction synchronization as appropriate.
* @see #newTransactionStatus * @see #newTransactionStatus
* @see #prepareTransactionStatus * @see #prepareTransactionStatus
*/ */
protected final DefaultTransactionStatus prepareTransactionStatus( private DefaultTransactionStatus prepareTransactionStatus(
TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction, TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) { boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
DefaultTransactionStatus status = newTransactionStatus( DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, newTransaction, newSynchronization, debug, suspendedResources); definition, transaction, newTransaction, newSynchronization, false, debug, suspendedResources);
prepareSynchronization(status, definition); prepareSynchronization(status, definition);
return status; return status;
} }
@@ -525,21 +558,20 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
/** /**
* Create a TransactionStatus instance for the given arguments. * Create a TransactionStatus instance for the given arguments.
*/ */
protected DefaultTransactionStatus newTransactionStatus( private DefaultTransactionStatus newTransactionStatus(
TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction, 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 && boolean actualNewSynchronization = newSynchronization &&
!TransactionSynchronizationManager.isSynchronizationActive(); !TransactionSynchronizationManager.isSynchronizationActive();
return new DefaultTransactionStatus( return new DefaultTransactionStatus(definition.getName(), transaction, newTransaction,
transaction, newTransaction, actualNewSynchronization, actualNewSynchronization, nested, definition.isReadOnly(), debug, suspendedResources);
definition.isReadOnly(), debug, suspendedResources);
} }
/** /**
* Initialize transaction synchronization as appropriate. * Initialize transaction synchronization as appropriate.
*/ */
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) { private void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
if (status.isNewSynchronization()) { if (status.isNewSynchronization()) {
TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction()); TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel( TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
@@ -734,6 +766,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
private void processCommit(DefaultTransactionStatus status) throws TransactionException { private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try { try {
boolean beforeCompletionInvoked = false; boolean beforeCompletionInvoked = false;
boolean commitListenerInvoked = false;
try { try {
boolean unexpectedRollback = false; boolean unexpectedRollback = false;
@@ -747,6 +780,8 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
logger.debug("Releasing transaction savepoint"); logger.debug("Releasing transaction savepoint");
} }
unexpectedRollback = status.isGlobalRollbackOnly(); unexpectedRollback = status.isGlobalRollbackOnly();
this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status));
commitListenerInvoked = true;
status.releaseHeldSavepoint(); status.releaseHeldSavepoint();
} }
else if (status.isNewTransaction()) { else if (status.isNewTransaction()) {
@@ -754,6 +789,8 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
logger.debug("Initiating transaction commit"); logger.debug("Initiating transaction commit");
} }
unexpectedRollback = status.isGlobalRollbackOnly(); unexpectedRollback = status.isGlobalRollbackOnly();
this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status));
commitListenerInvoked = true;
doCommit(status); doCommit(status);
} }
else if (isFailEarlyOnGlobalRollbackOnly()) { else if (isFailEarlyOnGlobalRollbackOnly()) {
@@ -768,17 +805,19 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
} }
} }
catch (UnexpectedRollbackException ex) { catch (UnexpectedRollbackException ex) {
// can only be caused by doCommit
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null));
throw ex; throw ex;
} }
catch (TransactionException ex) { catch (TransactionException ex) {
// can only be caused by doCommit
if (isRollbackOnCommitFailure()) { if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex); doRollbackOnCommitException(status, ex);
} }
else { else {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
if (commitListenerInvoked) {
this.transactionExecutionListeners.forEach(listener -> listener.afterCommit(status, ex));
}
} }
throw ex; throw ex;
} }
@@ -797,6 +836,9 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
} }
finally { finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); 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) { private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try { try {
boolean unexpectedRollback = unexpected; boolean unexpectedRollback = unexpected;
boolean rollbackListenerInvoked = false;
try { try {
triggerBeforeCompletion(status); triggerBeforeCompletion(status);
@@ -840,12 +883,16 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
if (status.isDebug()) { if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint"); logger.debug("Rolling back transaction to savepoint");
} }
this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status));
rollbackListenerInvoked = true;
status.rollbackToHeldSavepoint(); status.rollbackToHeldSavepoint();
} }
else if (status.isNewTransaction()) { else if (status.isNewTransaction()) {
if (status.isDebug()) { if (status.isDebug()) {
logger.debug("Initiating transaction rollback"); logger.debug("Initiating transaction rollback");
} }
this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status));
rollbackListenerInvoked = true;
doRollback(status); doRollback(status);
} }
else { else {
@@ -874,10 +921,16 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
} }
catch (RuntimeException | Error ex) { catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
if (rollbackListenerInvoked) {
this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, ex));
}
throw ex; throw ex;
} }
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); 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 // Raise UnexpectedRollbackException if we had a global rollback-only marker
if (unexpectedRollback) { if (unexpectedRollback) {
@@ -915,9 +968,11 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
catch (RuntimeException | Error rbex) { catch (RuntimeException | Error rbex) {
logger.error("Commit exception overridden by rollback exception", ex); logger.error("Commit exception overridden by rollback exception", ex);
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, rbex));
throw rbex; throw rbex;
} }
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null));
} }

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 @Override
public void setRollbackOnly() { public void setRollbackOnly() {
if (this.completed) {
throw new IllegalStateException("Transaction completed");
}
this.rollbackOnly = true; this.rollbackOnly = true;
} }
@@ -216,16 +219,4 @@ public abstract class AbstractTransactionStatus implements TransactionStatus {
throw new NestedTransactionNotSupportedException("This transaction does not support savepoints"); 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() {
}
} }

View File

@@ -50,6 +50,9 @@ import org.springframework.util.Assert;
*/ */
public class DefaultTransactionStatus extends AbstractTransactionStatus { public class DefaultTransactionStatus extends AbstractTransactionStatus {
@Nullable
private final String transactionName;
@Nullable @Nullable
private final Object transaction; private final Object transaction;
@@ -57,6 +60,8 @@ public class DefaultTransactionStatus extends AbstractTransactionStatus {
private final boolean newSynchronization; private final boolean newSynchronization;
private final boolean nested;
private final boolean readOnly; private final boolean readOnly;
private final boolean debug; private final boolean debug;
@@ -67,6 +72,7 @@ public class DefaultTransactionStatus extends AbstractTransactionStatus {
/** /**
* Create a new {@code DefaultTransactionStatus} instance. * Create a new {@code DefaultTransactionStatus} instance.
* @param transactionName the defined name of the transaction
* @param transaction underlying transaction object that can hold state * @param transaction underlying transaction object that can hold state
* for the internal transaction implementation * for the internal transaction implementation
* @param newTransaction if the transaction is new, otherwise participating * @param newTransaction if the transaction is new, otherwise participating
@@ -79,19 +85,35 @@ public class DefaultTransactionStatus extends AbstractTransactionStatus {
* debug logging should be enabled. * debug logging should be enabled.
* @param suspendedResources a holder for resources that have been suspended * @param suspendedResources a holder for resources that have been suspended
* for this transaction, if any * for this transaction, if any
* @since 6.1
*/ */
public DefaultTransactionStatus( public DefaultTransactionStatus(
@Nullable Object transaction, boolean newTransaction, boolean newSynchronization, @Nullable String transactionName, @Nullable Object transaction, boolean newTransaction,
boolean readOnly, boolean debug, @Nullable Object suspendedResources) { boolean newSynchronization, boolean nested, boolean readOnly, boolean debug,
@Nullable Object suspendedResources) {
this.transactionName = transactionName;
this.transaction = transaction; this.transaction = transaction;
this.newTransaction = newTransaction; this.newTransaction = newTransaction;
this.newSynchronization = newSynchronization; this.newSynchronization = newSynchronization;
this.nested = nested;
this.readOnly = readOnly; this.readOnly = readOnly;
this.debug = debug; this.debug = debug;
this.suspendedResources = suspendedResources; 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. * Return the underlying transaction object.
@@ -102,9 +124,7 @@ public class DefaultTransactionStatus extends AbstractTransactionStatus {
return this.transaction; return this.transaction;
} }
/** @Override
* Return whether there is an actual transaction active.
*/
public boolean hasTransaction() { public boolean hasTransaction() {
return (this.transaction != null); return (this.transaction != null);
} }
@@ -115,16 +135,18 @@ public class DefaultTransactionStatus extends AbstractTransactionStatus {
} }
/** /**
* Return if a new transaction synchronization has been opened * Return if a new transaction synchronization has been opened for this transaction.
* for this transaction.
*/ */
public boolean isNewSynchronization() { public boolean isNewSynchronization() {
return this.newSynchronization; return this.newSynchronization;
} }
/** @Override
* Return if this transaction is defined as read-only transaction. public boolean isNested() {
*/ return this.nested;
}
@Override
public boolean isReadOnly() { public boolean isReadOnly() {
return this.readOnly; return this.readOnly;
} }

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * return an internal rollback-only marker, typically from another
* transaction that has participated and marked it as rollback-only. * transaction that has participated and marked it as rollback-only.
* *
* <p>Autodetected by DefaultTransactionStatus, to always return a * <p>Autodetected by {@link DefaultTransactionStatus} in order to always
* current rollbackOnly flag even if not resulting from the current * return a current rollbackOnly flag even if not resulting from the current
* TransactionStatus. * TransactionStatus.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 1.1 * @since 1.1
* @see DefaultTransactionStatus#isRollbackOnly * @see DefaultTransactionStatus#isGlobalRollbackOnly()
*/ */
public interface SmartTransactionObject extends Flushable { public interface SmartTransactionObject extends Flushable {
/** /**
* Return whether the transaction is internally marked as rollback-only. * Return whether the transaction is internally marked as rollback-only.
* Can, for example, check the JTA UserTransaction. * Can, for example, check the JTA UserTransaction.
* <p>The default implementation returns {@code false}.
* @see jakarta.transaction.UserTransaction#getStatus * @see jakarta.transaction.UserTransaction#getStatus
* @see jakarta.transaction.Status#STATUS_MARKED_ROLLBACK * @see jakarta.transaction.Status#STATUS_MARKED_ROLLBACK
*/ */
boolean isRollbackOnly(); default boolean isRollbackOnly() {
return false;
}
/** /**
* Flush the underlying sessions to the datastore, if applicable: * Flush the underlying sessions to the datastore, if applicable:
* for example, all affected Hibernate/JPA sessions. * for example, all affected Hibernate/JPA sessions.
* <p>The default implementation is empty, considering flush as a no-op.
*/ */
@Override @Override
void flush(); default void flush() {
}
} }

View File

@@ -72,10 +72,17 @@ public class JtaTransactionManagerTests {
@Override @Override
protected void doInTransactionWithoutResult(TransactionStatus status) { protected void doInTransactionWithoutResult(TransactionStatus status) {
// something transactional // 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(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
TransactionSynchronizationManager.registerSynchronization(synch); TransactionSynchronizationManager.registerSynchronization(synch);
assertThat(TransactionSynchronizationManager.getCurrentTransactionName()).isEqualTo("txName"); assertThat(TransactionSynchronizationManager.getCurrentTransactionName()).isEqualTo("txName");
assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse();
assertThat(status.isRollbackOnly()).isFalse();
assertThat(status.isCompleted()).isFalse();
} }
}); });
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
@@ -105,8 +112,15 @@ public class JtaTransactionManagerTests {
@Override @Override
protected void doInTransactionWithoutResult(TransactionStatus status) { protected void doInTransactionWithoutResult(TransactionStatus status) {
// something transactional // 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(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
TransactionSynchronizationManager.registerSynchronization(synch); TransactionSynchronizationManager.registerSynchronization(synch);
assertThat(status.isRollbackOnly()).isFalse();
assertThat(status.isCompleted()).isFalse();
} }
}); });
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
@@ -134,7 +148,14 @@ public class JtaTransactionManagerTests {
tt.execute(new TransactionCallbackWithoutResult() { tt.execute(new TransactionCallbackWithoutResult() {
@Override @Override
protected void doInTransactionWithoutResult(TransactionStatus status) { 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(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
assertThat(status.isRollbackOnly()).isFalse();
assertThat(status.isCompleted()).isFalse();
} }
}); });
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
@@ -160,11 +181,18 @@ public class JtaTransactionManagerTests {
tt.execute(new TransactionCallbackWithoutResult() { tt.execute(new TransactionCallbackWithoutResult() {
@Override @Override
protected void doInTransactionWithoutResult(TransactionStatus status) { 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(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
TransactionSynchronizationManager.registerSynchronization(synch); TransactionSynchronizationManager.registerSynchronization(synch);
assertThat(TransactionSynchronizationManager.getCurrentTransactionName()).isEqualTo("txName"); assertThat(TransactionSynchronizationManager.getCurrentTransactionName()).isEqualTo("txName");
assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse();
status.setRollbackOnly(); status.setRollbackOnly();
assertThat(status.isRollbackOnly()).isTrue();
assertThat(status.isCompleted()).isFalse();
} }
}); });
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
@@ -192,9 +220,16 @@ public class JtaTransactionManagerTests {
tt.execute(new TransactionCallbackWithoutResult() { tt.execute(new TransactionCallbackWithoutResult() {
@Override @Override
protected void doInTransactionWithoutResult(TransactionStatus status) { 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(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
TransactionSynchronizationManager.registerSynchronization(synch); TransactionSynchronizationManager.registerSynchronization(synch);
status.setRollbackOnly(); status.setRollbackOnly();
assertThat(status.isRollbackOnly()).isTrue();
assertThat(status.isCompleted()).isFalse();
} }
}); });
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
@@ -221,8 +256,16 @@ public class JtaTransactionManagerTests {
tt.execute(new TransactionCallbackWithoutResult() { tt.execute(new TransactionCallbackWithoutResult() {
@Override @Override
protected void doInTransactionWithoutResult(TransactionStatus status) { 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(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
assertThat(status.isRollbackOnly()).isFalse();
status.setRollbackOnly(); status.setRollbackOnly();
assertThat(status.isRollbackOnly()).isTrue();
assertThat(status.isCompleted()).isFalse();
} }
}); });
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
@@ -808,6 +851,13 @@ public class JtaTransactionManagerTests {
@Override @Override
protected void doInTransactionWithoutResult(TransactionStatus status) { protected void doInTransactionWithoutResult(TransactionStatus status) {
// something transactional // 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); given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION);
willThrow(new SystemException("system exception")).given(ut).begin(); willThrow(new SystemException("system exception")).given(ut).begin();
JtaTransactionManager ptm = newJtaTransactionManager(ut);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
ptm.addListener(tl);
assertThatExceptionOfType(CannotCreateTransactionException.class).isThrownBy(() -> { assertThatExceptionOfType(CannotCreateTransactionException.class).isThrownBy(() -> {
JtaTransactionManager ptm = newJtaTransactionManager(ut);
TransactionTemplate tt = new TransactionTemplate(ptm); TransactionTemplate tt = new TransactionTemplate(ptm);
tt.execute(new TransactionCallbackWithoutResult() { tt.execute(new TransactionCallbackWithoutResult() {
@Override @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 @Test
@@ -878,8 +941,11 @@ public class JtaTransactionManagerTests {
Status.STATUS_ACTIVE, Status.STATUS_ACTIVE); Status.STATUS_ACTIVE, Status.STATUS_ACTIVE);
willThrow(new RollbackException("unexpected rollback")).given(ut).commit(); willThrow(new RollbackException("unexpected rollback")).given(ut).commit();
JtaTransactionManager ptm = newJtaTransactionManager(ut);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
ptm.addListener(tl);
assertThatExceptionOfType(UnexpectedRollbackException.class).isThrownBy(() -> { assertThatExceptionOfType(UnexpectedRollbackException.class).isThrownBy(() -> {
JtaTransactionManager ptm = newJtaTransactionManager(ut);
TransactionTemplate tt = new TransactionTemplate(ptm); TransactionTemplate tt = new TransactionTemplate(ptm);
tt.execute(new TransactionCallbackWithoutResult() { tt.execute(new TransactionCallbackWithoutResult() {
@Override @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(); verify(ut).begin();
} }
@@ -1023,8 +1099,11 @@ public class JtaTransactionManagerTests {
Status.STATUS_ACTIVE, Status.STATUS_ACTIVE); Status.STATUS_ACTIVE, Status.STATUS_ACTIVE);
willThrow(new SystemException("system exception")).given(ut).commit(); willThrow(new SystemException("system exception")).given(ut).commit();
JtaTransactionManager ptm = newJtaTransactionManager(ut);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
ptm.addListener(tl);
assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() -> { assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() -> {
JtaTransactionManager ptm = newJtaTransactionManager(ut);
TransactionTemplate tt = new TransactionTemplate(ptm); TransactionTemplate tt = new TransactionTemplate(ptm);
tt.execute(new TransactionCallbackWithoutResult() { tt.execute(new TransactionCallbackWithoutResult() {
@Override @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(); verify(ut).begin();
} }
@@ -1050,8 +1139,11 @@ public class JtaTransactionManagerTests {
given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE); given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE);
willThrow(new SystemException("system exception")).given(ut).rollback(); willThrow(new SystemException("system exception")).given(ut).rollback();
JtaTransactionManager ptm = newJtaTransactionManager(ut);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
ptm.addListener(tl);
assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() -> { assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() -> {
JtaTransactionManager ptm = newJtaTransactionManager(ut);
TransactionTemplate tt = new TransactionTemplate(ptm); TransactionTemplate tt = new TransactionTemplate(ptm);
tt.execute(new TransactionCallbackWithoutResult() { tt.execute(new TransactionCallbackWithoutResult() {
@Override @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(); verify(ut).begin();
} }

View File

@@ -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;
}
}

View File

@@ -16,8 +16,11 @@
package org.springframework.transaction.reactive; package org.springframework.transaction.reactive;
import java.util.function.Function;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.lang.Nullable;
import org.springframework.transaction.CannotCreateTransactionException; import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.ReactiveTransactionManager; import org.springframework.transaction.ReactiveTransactionManager;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
@@ -26,6 +29,7 @@ import org.springframework.transaction.TransactionDefinition;
* Test implementation of a {@link ReactiveTransactionManager}. * Test implementation of a {@link ReactiveTransactionManager}.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Juergen Hoeller
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
class ReactiveTestTransactionManager extends AbstractReactiveTransactionManager { class ReactiveTestTransactionManager extends AbstractReactiveTransactionManager {
@@ -36,7 +40,11 @@ class ReactiveTestTransactionManager extends AbstractReactiveTransactionManager
private final boolean canCreateTransaction; private final boolean canCreateTransaction;
private final boolean forceFailOnCommit; @Nullable
private Function<String, RuntimeException> forceFailOnCommit;
@Nullable
private Function<String, RuntimeException> forceFailOnRollback;
protected boolean begin = false; protected boolean begin = false;
@@ -50,13 +58,15 @@ class ReactiveTestTransactionManager extends AbstractReactiveTransactionManager
ReactiveTestTransactionManager(boolean existingTransaction, boolean canCreateTransaction) { ReactiveTestTransactionManager(boolean existingTransaction, boolean canCreateTransaction) {
this(existingTransaction, canCreateTransaction, false);
}
ReactiveTestTransactionManager(boolean existingTransaction, boolean canCreateTransaction, boolean forceFailOnCommit) {
this.existingTransaction = existingTransaction; this.existingTransaction = existingTransaction;
this.canCreateTransaction = canCreateTransaction; this.canCreateTransaction = canCreateTransaction;
}
ReactiveTestTransactionManager(boolean existingTransaction, @Nullable Function<String, RuntimeException> forceFailOnCommit, @Nullable Function<String, RuntimeException> forceFailOnRollback) {
this.existingTransaction = existingTransaction;
this.canCreateTransaction = true;
this.forceFailOnCommit = forceFailOnCommit; this.forceFailOnCommit = forceFailOnCommit;
this.forceFailOnRollback = forceFailOnRollback;
} }
@@ -88,8 +98,8 @@ class ReactiveTestTransactionManager extends AbstractReactiveTransactionManager
} }
return Mono.fromRunnable(() -> { return Mono.fromRunnable(() -> {
this.commit = true; this.commit = true;
if (this.forceFailOnCommit) { if (this.forceFailOnCommit != null) {
throw new IllegalArgumentException("Forced failure on commit"); throw this.forceFailOnCommit.apply("Forced failure on commit");
} }
}); });
} }
@@ -99,7 +109,12 @@ class ReactiveTestTransactionManager extends AbstractReactiveTransactionManager
if (!TRANSACTION.equals(status.getTransaction())) { if (!TRANSACTION.equals(status.getTransaction())) {
return Mono.error(new IllegalArgumentException("Not the same transaction object")); 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 @Override

View File

@@ -21,10 +21,14 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.test.StepVerifier; import reactor.test.StepVerifier;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.IllegalTransactionStateException; import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.ReactiveTransaction; import org.springframework.transaction.ReactiveTransaction;
import org.springframework.transaction.ReactiveTransactionManager; import org.springframework.transaction.ReactiveTransactionManager;
import org.springframework.transaction.TestTransactionExecutionListener;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.transaction.support.DefaultTransactionDefinition;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -86,6 +90,9 @@ public class ReactiveTransactionSupportTests {
@Test @Test
public void commitWithoutExistingTransaction() { public void commitWithoutExistingTransaction() {
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true); ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
tm.addListener(tl);
tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::commit) tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::commit)
.contextWrite(TransactionContextManager.createTransactionContext()) .contextWrite(TransactionContextManager.createTransactionContext())
.as(StepVerifier::create).verifyComplete(); .as(StepVerifier::create).verifyComplete();
@@ -95,11 +102,21 @@ public class ReactiveTransactionSupportTests {
assertHasNoRollback(tm); assertHasNoRollback(tm);
assertHasNotSetRollbackOnly(tm); assertHasNotSetRollbackOnly(tm);
assertHasCleanedUp(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 @Test
public void rollbackWithoutExistingTransaction() { public void rollbackWithoutExistingTransaction() {
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true); ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
tm.addListener(tl);
tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::rollback) tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::rollback)
.contextWrite(TransactionContextManager.createTransactionContext()).as(StepVerifier::create) .contextWrite(TransactionContextManager.createTransactionContext()).as(StepVerifier::create)
.verifyComplete(); .verifyComplete();
@@ -109,11 +126,21 @@ public class ReactiveTransactionSupportTests {
assertHasRolledBack(tm); assertHasRolledBack(tm);
assertHasNotSetRollbackOnly(tm); assertHasNotSetRollbackOnly(tm);
assertHasCleanedUp(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 @Test
public void rollbackOnlyWithoutExistingTransaction() { public void rollbackOnlyWithoutExistingTransaction() {
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true); ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
tm.addListener(tl);
tm.getReactiveTransaction(new DefaultTransactionDefinition()).doOnNext(ReactiveTransaction::setRollbackOnly) tm.getReactiveTransaction(new DefaultTransactionDefinition()).doOnNext(ReactiveTransaction::setRollbackOnly)
.flatMap(tm::commit) .flatMap(tm::commit)
.contextWrite(TransactionContextManager.createTransactionContext()).as(StepVerifier::create) .contextWrite(TransactionContextManager.createTransactionContext()).as(StepVerifier::create)
@@ -124,11 +151,21 @@ public class ReactiveTransactionSupportTests {
assertHasRolledBack(tm); assertHasRolledBack(tm);
assertHasNotSetRollbackOnly(tm); assertHasNotSetRollbackOnly(tm);
assertHasCleanedUp(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 @Test
public void commitWithExistingTransaction() { public void commitWithExistingTransaction() {
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(true, true); ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(true, true);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
tm.addListener(tl);
tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::commit) tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::commit)
.contextWrite(TransactionContextManager.createTransactionContext()) .contextWrite(TransactionContextManager.createTransactionContext())
.as(StepVerifier::create).verifyComplete(); .as(StepVerifier::create).verifyComplete();
@@ -138,11 +175,21 @@ public class ReactiveTransactionSupportTests {
assertHasNoRollback(tm); assertHasNoRollback(tm);
assertHasNotSetRollbackOnly(tm); assertHasNotSetRollbackOnly(tm);
assertHasNotCleanedUp(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 @Test
public void rollbackWithExistingTransaction() { public void rollbackWithExistingTransaction() {
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(true, true); ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(true, true);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
tm.addListener(tl);
tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::rollback) tm.getReactiveTransaction(new DefaultTransactionDefinition()).flatMap(tm::rollback)
.contextWrite(TransactionContextManager.createTransactionContext()).as(StepVerifier::create) .contextWrite(TransactionContextManager.createTransactionContext()).as(StepVerifier::create)
.verifyComplete(); .verifyComplete();
@@ -152,11 +199,21 @@ public class ReactiveTransactionSupportTests {
assertHasNoRollback(tm); assertHasNoRollback(tm);
assertHasSetRollbackOnly(tm); assertHasSetRollbackOnly(tm);
assertHasNotCleanedUp(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 @Test
public void rollbackOnlyWithExistingTransaction() { public void rollbackOnlyWithExistingTransaction() {
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(true, true); ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(true, true);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
tm.addListener(tl);
tm.getReactiveTransaction(new DefaultTransactionDefinition()).doOnNext(ReactiveTransaction::setRollbackOnly).flatMap(tm::commit) tm.getReactiveTransaction(new DefaultTransactionDefinition()).doOnNext(ReactiveTransaction::setRollbackOnly).flatMap(tm::commit)
.contextWrite(TransactionContextManager.createTransactionContext()).as(StepVerifier::create) .contextWrite(TransactionContextManager.createTransactionContext()).as(StepVerifier::create)
.verifyComplete(); .verifyComplete();
@@ -166,6 +223,13 @@ public class ReactiveTransactionSupportTests {
assertHasNoRollback(tm); assertHasNoRollback(tm);
assertHasSetRollbackOnly(tm); assertHasSetRollbackOnly(tm);
assertHasNotCleanedUp(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 @Test
@@ -203,10 +267,11 @@ public class ReactiveTransactionSupportTests {
assertHasCleanedUp(tm); assertHasCleanedUp(tm);
} }
//gh-28968 @Test // gh-28968
@Test
void errorInCommitDoesInitiateRollbackAfterCommit() { 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); TransactionalOperator rxtx = TransactionalOperator.create(tm);
StepVerifier.create(rxtx.transactional(Mono.just("bar"))) StepVerifier.create(rxtx.transactional(Mono.just("bar")))
@@ -217,8 +282,79 @@ public class ReactiveTransactionSupportTests {
assertHasRolledBack(tm); assertHasRolledBack(tm);
assertHasNotSetRollbackOnly(tm); assertHasNotSetRollbackOnly(tm);
assertHasCleanedUp(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) { private void assertHasBegan(ReactiveTestTransactionManager actual) {
assertThat(actual.begin).as("Expected <ReactiveTransactionManager.begin()> but was <begin()> was not invoked").isTrue(); assertThat(actual.begin).as("Expected <ReactiveTransactionManager.begin()> but was <begin()> was not invoked").isTrue();
} }

View File

@@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.transaction.IllegalTransactionStateException; import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.MockCallbackPreferringTransactionManager; import org.springframework.transaction.MockCallbackPreferringTransactionManager;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TestTransactionExecutionListener;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSystemException; import org.springframework.transaction.TransactionSystemException;
@@ -63,6 +64,7 @@ class TransactionSupportTests {
@Test @Test
void noExistingTransaction() { void noExistingTransaction() {
PlatformTransactionManager tm = new TestTransactionManager(false, true); PlatformTransactionManager tm = new TestTransactionManager(false, true);
DefaultTransactionStatus status1 = (DefaultTransactionStatus) DefaultTransactionStatus status1 = (DefaultTransactionStatus)
tm.getTransaction(new DefaultTransactionDefinition(PROPAGATION_SUPPORTS)); tm.getTransaction(new DefaultTransactionDefinition(PROPAGATION_SUPPORTS));
assertThat(status1.hasTransaction()).as("Must not have transaction").isFalse(); 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.hasTransaction()).as("Must have transaction").isTrue();
assertThat(status2.isNewTransaction()).as("Must be new transaction").isTrue(); assertThat(status2.isNewTransaction()).as("Must be new transaction").isTrue();
assertThatExceptionOfType(IllegalTransactionStateException.class).isThrownBy(() -> assertThatExceptionOfType(IllegalTransactionStateException.class)
tm.getTransaction(new DefaultTransactionDefinition(PROPAGATION_MANDATORY))); .isThrownBy(() -> tm.getTransaction(new DefaultTransactionDefinition(PROPAGATION_MANDATORY)));
} }
@Test @Test
void existingTransaction() { void existingTransaction() {
PlatformTransactionManager tm = new TestTransactionManager(true, true); PlatformTransactionManager tm = new TestTransactionManager(true, true);
DefaultTransactionStatus status1 = (DefaultTransactionStatus) DefaultTransactionStatus status1 = (DefaultTransactionStatus)
tm.getTransaction(new DefaultTransactionDefinition(PROPAGATION_SUPPORTS)); tm.getTransaction(new DefaultTransactionDefinition(PROPAGATION_SUPPORTS));
assertThat(status1.getTransaction()).as("Must have transaction").isNotNull(); assertThat(status1.getTransaction()).as("Must have transaction").isNotNull();
@@ -98,6 +101,9 @@ class TransactionSupportTests {
@Test @Test
void commitWithoutExistingTransaction() { void commitWithoutExistingTransaction() {
TestTransactionManager tm = new TestTransactionManager(false, true); TestTransactionManager tm = new TestTransactionManager(false, true);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
tm.addListener(tl);
TransactionStatus status = tm.getTransaction(null); TransactionStatus status = tm.getTransaction(null);
tm.commit(status); tm.commit(status);
@@ -105,11 +111,21 @@ class TransactionSupportTests {
assertThat(tm.commit).as("triggered commit").isTrue(); assertThat(tm.commit).as("triggered commit").isTrue();
assertThat(tm.rollback).as("no rollback").isFalse(); assertThat(tm.rollback).as("no rollback").isFalse();
assertThat(tm.rollbackOnly).as("no rollbackOnly").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 @Test
void rollbackWithoutExistingTransaction() { void rollbackWithoutExistingTransaction() {
TestTransactionManager tm = new TestTransactionManager(false, true); TestTransactionManager tm = new TestTransactionManager(false, true);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
tm.addListener(tl);
TransactionStatus status = tm.getTransaction(null); TransactionStatus status = tm.getTransaction(null);
tm.rollback(status); tm.rollback(status);
@@ -117,11 +133,21 @@ class TransactionSupportTests {
assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.commit).as("no commit").isFalse();
assertThat(tm.rollback).as("triggered rollback").isTrue(); assertThat(tm.rollback).as("triggered rollback").isTrue();
assertThat(tm.rollbackOnly).as("no rollbackOnly").isFalse(); 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 @Test
void rollbackOnlyWithoutExistingTransaction() { void rollbackOnlyWithoutExistingTransaction() {
TestTransactionManager tm = new TestTransactionManager(false, true); TestTransactionManager tm = new TestTransactionManager(false, true);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
tm.addListener(tl);
TransactionStatus status = tm.getTransaction(null); TransactionStatus status = tm.getTransaction(null);
status.setRollbackOnly(); status.setRollbackOnly();
tm.commit(status); tm.commit(status);
@@ -130,11 +156,21 @@ class TransactionSupportTests {
assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.commit).as("no commit").isFalse();
assertThat(tm.rollback).as("triggered rollback").isTrue(); assertThat(tm.rollback).as("triggered rollback").isTrue();
assertThat(tm.rollbackOnly).as("no rollbackOnly").isFalse(); 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 @Test
void commitWithExistingTransaction() { void commitWithExistingTransaction() {
TestTransactionManager tm = new TestTransactionManager(true, true); TestTransactionManager tm = new TestTransactionManager(true, true);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
tm.addListener(tl);
TransactionStatus status = tm.getTransaction(null); TransactionStatus status = tm.getTransaction(null);
tm.commit(status); tm.commit(status);
@@ -142,11 +178,21 @@ class TransactionSupportTests {
assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.commit).as("no commit").isFalse();
assertThat(tm.rollback).as("no rollback").isFalse(); assertThat(tm.rollback).as("no rollback").isFalse();
assertThat(tm.rollbackOnly).as("no rollbackOnly").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 @Test
void rollbackWithExistingTransaction() { void rollbackWithExistingTransaction() {
TestTransactionManager tm = new TestTransactionManager(true, true); TestTransactionManager tm = new TestTransactionManager(true, true);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
tm.addListener(tl);
TransactionStatus status = tm.getTransaction(null); TransactionStatus status = tm.getTransaction(null);
tm.rollback(status); tm.rollback(status);
@@ -154,11 +200,21 @@ class TransactionSupportTests {
assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.commit).as("no commit").isFalse();
assertThat(tm.rollback).as("no rollback").isFalse(); assertThat(tm.rollback).as("no rollback").isFalse();
assertThat(tm.rollbackOnly).as("triggered rollbackOnly").isTrue(); 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 @Test
void rollbackOnlyWithExistingTransaction() { void rollbackOnlyWithExistingTransaction() {
TestTransactionManager tm = new TestTransactionManager(true, true); TestTransactionManager tm = new TestTransactionManager(true, true);
TestTransactionExecutionListener tl = new TestTransactionExecutionListener();
tm.addListener(tl);
TransactionStatus status = tm.getTransaction(null); TransactionStatus status = tm.getTransaction(null);
status.setRollbackOnly(); status.setRollbackOnly();
tm.commit(status); tm.commit(status);
@@ -167,6 +223,13 @@ class TransactionSupportTests {
assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.commit).as("no commit").isFalse();
assertThat(tm.rollback).as("no rollback").isFalse(); assertThat(tm.rollback).as("no rollback").isFalse();
assertThat(tm.rollbackOnly).as("triggered rollbackOnly").isTrue(); 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 @Test
@@ -204,14 +267,14 @@ class TransactionSupportTests {
TestTransactionManager tm = new TestTransactionManager(false, true); TestTransactionManager tm = new TestTransactionManager(false, true);
TransactionTemplate template = new TransactionTemplate(tm); TransactionTemplate template = new TransactionTemplate(tm);
RuntimeException ex = new RuntimeException("Some application exception"); RuntimeException ex = new RuntimeException("Some application exception");
assertThatRuntimeException().isThrownBy(() -> assertThatRuntimeException()
template.execute(new TransactionCallbackWithoutResult() { .isThrownBy(() -> template.execute(new TransactionCallbackWithoutResult() {
@Override @Override
protected void doInTransactionWithoutResult(TransactionStatus status) { protected void doInTransactionWithoutResult(TransactionStatus status) {
throw ex; throw ex;
} }
})) }))
.isSameAs(ex); .isSameAs(ex);
assertThat(tm.begin).as("triggered begin").isTrue(); assertThat(tm.begin).as("triggered begin").isTrue();
assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.commit).as("no commit").isFalse();
assertThat(tm.rollback).as("triggered rollback").isTrue(); assertThat(tm.rollback).as("triggered rollback").isTrue();
@@ -232,8 +295,8 @@ class TransactionSupportTests {
TransactionTemplate template = new TransactionTemplate(tm); TransactionTemplate template = new TransactionTemplate(tm);
RuntimeException ex = new RuntimeException("Some application exception"); RuntimeException ex = new RuntimeException("Some application exception");
assertThatRuntimeException() assertThatRuntimeException()
.isThrownBy(() -> template.executeWithoutResult(status -> { throw ex; })) .isThrownBy(() -> template.executeWithoutResult(status -> { throw ex; }))
.isSameAs(tex); .isSameAs(tex);
assertThat(tm.begin).as("triggered begin").isTrue(); assertThat(tm.begin).as("triggered begin").isTrue();
assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.commit).as("no commit").isFalse();
assertThat(tm.rollback).as("triggered rollback").isTrue(); assertThat(tm.rollback).as("triggered rollback").isTrue();
@@ -245,7 +308,7 @@ class TransactionSupportTests {
TestTransactionManager tm = new TestTransactionManager(false, true); TestTransactionManager tm = new TestTransactionManager(false, true);
TransactionTemplate template = new TransactionTemplate(tm); TransactionTemplate template = new TransactionTemplate(tm);
assertThatExceptionOfType(Error.class) 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.begin).as("triggered begin").isTrue();
assertThat(tm.commit).as("no commit").isFalse(); assertThat(tm.commit).as("no commit").isFalse();
assertThat(tm.rollback).as("triggered rollback").isTrue(); assertThat(tm.rollback).as("triggered rollback").isTrue();
@@ -265,12 +328,12 @@ class TransactionSupportTests {
assertThat(template3).isEqualTo(template2); assertThat(template3).isEqualTo(template2);
} }
@Nested @Nested
class AbstractPlatformTransactionManagerConfigurationTests { class AbstractPlatformTransactionManagerConfigurationTests {
private final AbstractPlatformTransactionManager tm = new TestTransactionManager(false, true); private final AbstractPlatformTransactionManager tm = new TestTransactionManager(false, true);
@Test @Test
void setTransactionSynchronizationNameToUnsupportedValues() { void setTransactionSynchronizationNameToUnsupportedValues() {
assertThatIllegalArgumentException().isThrownBy(() -> tm.setTransactionSynchronizationName(null)); assertThatIllegalArgumentException().isThrownBy(() -> tm.setTransactionSynchronizationName(null));
@@ -302,22 +365,20 @@ class TransactionSupportTests {
assertThat(tm.getTransactionSynchronization()).isEqualTo(SYNCHRONIZATION_ON_ACTUAL_TRANSACTION); assertThat(tm.getTransactionSynchronization()).isEqualTo(SYNCHRONIZATION_ON_ACTUAL_TRANSACTION);
} }
private static Stream<String> streamSynchronizationConstants() { private static Stream<String> streamSynchronizationConstants() {
return Arrays.stream(AbstractPlatformTransactionManager.class.getFields()) return Arrays.stream(AbstractPlatformTransactionManager.class.getFields())
.filter(ReflectionUtils::isPublicStaticFinal) .filter(ReflectionUtils::isPublicStaticFinal)
.map(Field::getName) .map(Field::getName)
.filter(name -> name.startsWith("SYNCHRONIZATION_")); .filter(name -> name.startsWith("SYNCHRONIZATION_"));
} }
} }
@Nested @Nested
class TransactionTemplateConfigurationTests { class TransactionTemplateConfigurationTests {
private final TransactionTemplate template = new TransactionTemplate(); private final TransactionTemplate template = new TransactionTemplate();
@Test @Test
void setTransactionManager() { void setTransactionManager() {
TestTransactionManager tm = new TestTransactionManager(false, true); TestTransactionManager tm = new TestTransactionManager(false, true);
@@ -422,7 +483,6 @@ class TransactionSupportTests {
.filter(ReflectionUtils::isPublicStaticFinal) .filter(ReflectionUtils::isPublicStaticFinal)
.map(Field::getName); .map(Field::getName);
} }
} }
} }