This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Framework 6.2.0! |
Rolling Back a Declarative Transaction
The previous section outlined the basics of how to specify transactional settings for
classes, typically service layer classes, declaratively in your application. This section
describes how you can control the rollback of transactions in a simple, declarative
fashion in XML configuration. For details on controlling rollback semantics declaratively
with the @Transactional
annotation, see
@Transactional
Settings.
The recommended way to indicate to the Spring Framework’s transaction infrastructure
that a transaction’s work is to be rolled back is to throw an Exception
from code that
is currently executing in the context of a transaction. The Spring Framework’s
transaction infrastructure code catches any unhandled Exception
as it bubbles up
the call stack and makes a determination whether to mark the transaction for rollback.
In its default configuration, the Spring Framework’s transaction infrastructure code
marks a transaction for rollback only in the case of runtime, unchecked exceptions.
That is, when the thrown exception is an instance or subclass of RuntimeException
.
(Error
instances also, by default, result in a rollback).
The default configuration also provides support for Vavr’s Try
method to trigger
transaction rollbacks when it returns a 'Failure'.
This allows you to handle functional-style errors using Try and have the transaction
automatically rolled back in case of a failure. For more information on Vavr’s Try,
refer to the official Vavr documentation.
Here’s an example of how to use Vavr’s Try with a transactional method:
-
Java
@Transactional
public Try<String> myTransactionalMethod() {
// If myDataAccessOperation throws an exception, it will be caught by the
// Try instance created with Try.of() and wrapped inside the Failure class
// which can be checked using the isFailure() method on the Try instance.
return Try.of(delegate::myDataAccessOperation);
}
As of Spring Framework 6.1, there is also special treatment of CompletableFuture
(and general Future
) return values, triggering a rollback for such a handle if it
was exceptionally completed at the time of being returned from the original method.
This is intended for @Async
methods where the actual method implementation may
need to comply with a CompletableFuture
signature (auto-adapted to an actual
asynchronous handle for a call to the proxy by @Async
processing at runtime),
preferring exposure in the returned handle rather than rethrowing an exception:
-
Java
@Transactional @Async
public CompletableFuture<String> myTransactionalMethod() {
try {
return CompletableFuture.completedFuture(delegate.myDataAccessOperation());
}
catch (DataAccessException ex) {
return CompletableFuture.failedFuture(ex);
}
}
Checked exceptions that are thrown from a transactional method do not result in a rollback
in the default configuration. You can configure exactly which Exception
types mark a
transaction for rollback, including checked exceptions by specifying rollback rules.
Rollback rules
Rollback rules determine if a transaction should be rolled back when a given exception is thrown, and the rules are based on exception types or exception patterns. Rollback rules may be configured in XML via the When a rollback rule is defined with an exception type, that type will be used to match
against the type of a thrown exception and its super types, providing type safety and
avoiding any unintentional matches that may occur when using a pattern. For example, a
value of When a rollback rule is defined with an exception pattern, the pattern can be a fully
qualified class name or a substring of a fully qualified class name for an exception type
(which must be a subclass of
|
The following XML snippet demonstrates how to configure rollback for a checked,
application-specific Exception
type by supplying an exception pattern via the
rollback-for
attribute:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
If you do not want a transaction rolled back when an exception is thrown, you can also
specify 'no rollback' rules. The following example tells the Spring Framework’s
transaction infrastructure to commit the attendant transaction even in the face of an
unhandled InstrumentNotFoundException
:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
When the Spring Framework’s transaction infrastructure catches an exception and consults
the configured rollback rules to determine whether to mark the transaction for rollback,
the strongest matching rule wins. So, in the case of the following configuration, any
exception other than an InstrumentNotFoundException
results in a rollback of the
attendant transaction:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
</tx:attributes>
</tx:advice>
You can also indicate a required rollback programmatically. Although simple, this process is quite invasive and tightly couples your code to the Spring Framework’s transaction infrastructure. The following example shows how to programmatically indicate a required rollback:
-
Java
-
Kotlin
public void resolvePosition() {
try {
// some business logic...
} catch (NoProductInStockException ex) {
// trigger rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
fun resolvePosition() {
try {
// some business logic...
} catch (ex: NoProductInStockException) {
// trigger rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
You are strongly encouraged to use the declarative approach to rollback, if at all possible. Programmatic rollback is available should you absolutely need it, but its usage flies in the face of achieving a clean POJO-based architecture.