Rolling Back a Declarative Transaction
Spring 框架默认情况下会在遇到未处理的异常时回滚事务,包括运行时异常和 Error。从 Spring 框架 5.2 开始,还支持 Vavr 的 Try 方法,当其返回 "失败" 时触发回滚。自 Spring 框架 6.1 起,对 CompletableFuture 值的支持也得到了专门处理,如果它们在原始方法返回时已异常完成,则会触发回滚。
可以通过定义回滚规则来配置哪些异常类型会标记事务进行回滚,这些规则可以基于异常类型或模式。当回滚规则使用异常模式定义时,基于模式的回滚规则可能会导致意外匹配,因此必须仔细考虑模式的具体程度。
本文还简要提到了以编程方式指示必需的回滚,但强烈建议尽可能使用声明式方法。
前一节概述了如何以声明式的方式在应用程序中特定事务设置(通常是服务层类)的基本知识。本节介绍如何在 XML 配置中以简单、声明式的方式控制事务的回滚。有关使用 @Transactional
注解从声明式方式控制回滚语义的详细信息,请参见@Transactional
Settings。
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.
向 Spring 框架的事务基础架构指示事务的工作将回滚的推荐方法是从当前在事务上下文中执行的代码中抛出一个 Exception
。Spring 框架的事务基础架构代码会捕获任何未处理的 Exception
,因为它会在调用堆栈中冒泡并确定是否将事务标记为回滚。
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.
在其默认配置中,Spring 框架的事务基础架构代码仅在运行时未检查异常的情况下才会将事务标记为回滚。也就是说,当抛出的异常是 RuntimeException
的实例或子类时。(Error
实例也默认会导致回滚。)
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).
在 Spring Framework 5.2 中,默认配置还支持 Vavr 的 Try
方法,当返回“失败”时触发事务回滚。这允许你使用 Try 处理函数式样式的错误,并且在发生故障时自动回滚事务。有关 Vavr 的 Try 的更多信息,请参阅 official Vavr documentation。以下是如何将 Vavr 的 Try 与事务方法配合使用的示例:
As of Spring Framework 5.2, 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);
}
从 Spring Framework 6.1 开始,对 CompletableFuture
(和通用 Future
)返回值也进行了专门处理,如果在原始方法返回时它已异常完成,则会触发对该句柄的回滚。这是针对 @Async
方法设计的,其中实际方法实现可能需要符合 CompletableFuture
签名(运行时通过 @Async
处理自动适配到调用的实际异步句柄),宁愿在返回的句柄中公开也不愿重新抛出异常:
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);
}
}
从事务方法抛出的已检查异常不会导致默认配置中的回滚。你可以配置哪些 Exception
类型标记事务进行回滚,包括通过指定 回滚规则 来检查异常。
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 |
你必须仔细考虑模式的具体程度以及是否包含包信息(这不是强制性的)。例如,"Exception"
将几乎匹配所有内容,并且可能会隐藏其他规则。如果 "Exception"
旨在为所有已检查异常定义规则,则 "java.lang.Exception"
是正确的。对于名称更独特的异常,例如 "BaseBusinessException"
,可能无需对异常模式使用完全限定类名。
You must carefully consider how specific a pattern is and whether to include package
information (which isn’t mandatory). For example, "Exception"
will match nearly
anything and will probably hide other rules. "java.lang.Exception"
would be correct if
"Exception"
were meant to define a rule for all checked exceptions. With more unique
exception names such as "BaseBusinessException"
there is likely no need to use the
fully qualified class name for the exception pattern.
此外,基于模式的回滚规则可能会导致对类似命名的异常和嵌套类进行意外匹配。这是因为如果抛出异常的名称包含为回滚规则配置的异常模式,则认为抛出异常与给定的基于模式的回滚规则匹配。例如,给定一个配置为与 "com.example.CustomException"
匹配的规则,该规则将与名为 com.example.CustomExceptionV2
(与 CustomException
在同一包中但带有一个附加后缀)的异常或名为 com.example.CustomException$AnotherException
(在 CustomException
中声明为嵌套类的异常)的异常匹配。
Furthermore, pattern-based rollback rules may result in unintentional matches for
similarly named exceptions and nested classes. This is due to the fact that a thrown
exception is considered to be a match for a given pattern-based rollback rule if the name
of the thrown exception contains the exception pattern configured for the rollback rule.
For example, given a rule configured to match on "com.example.CustomException"
, that
rule will match against an exception named com.example.CustomExceptionV2
(an exception
in the same package as CustomException
but with an additional suffix) or an exception
named com.example.CustomException$AnotherException
(an exception declared as a nested
class in CustomException
).
以下 XML 片段演示如何通过 rollback-for
属性提供 异常模式 来配置特定于应用程序的已检查 Exception
类型的回滚:
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>
如果你不希望在抛出异常时回滚事务,你还可以指定“无回滚”规则。以下示例告诉 Spring Framework 的事务基础设施,即使遇到未处理的 InstrumentNotFoundException
,也要提交随后的事务:
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>
当 Spring Framework 的事务基础设施捕获异常并查阅已配置的回滚规则以确定是否标记事务进行回滚时,最强的匹配规则获胜。因此,在以下配置的情况下,除了 InstrumentNotFoundException
之外的任何异常都会导致随后事务的回滚:
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>
你还可以以编程方式指示必需的回滚。虽然很简单,但这个过程相当侵入,并严格地将你的代码与 Spring Framework 的事务基础设施耦合在一起。以下示例展示如何以编程方式指示必需的回滚:
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();
}
}
强烈建议你尽可能使用声明式方法进行回滚。如果你绝对需要,则可以进行编程回滚,但它的用法与实现干净的基于 POJO 的架构背道而驰。
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.