回滚声明式事务
上一节概述了如何在应用程序中以声明方式为类(通常是服务层类)指定事务设置的基础知识。本节将介绍如何在 XML 配置中以简单、声明的方式控制事务的回滚。有关使用 @Transactional
注解以声明方式控制回滚语义的详细信息,请参阅 @Transactional 设置。
向 Spring 框架的事务基础设施表明事务工作应回滚的推荐方式是,从当前在事务上下文中执行的代码中抛出 Exception
。Spring 框架的事务基础设施代码会在调用栈中捕获任何未处理的 Exception
,并决定是否将事务标记为回滚。
在默认配置下,Spring Framework 的事务基础设施代码仅在运行时、未检查的异常情况下标记事务进行回滚。也就是说,当抛出的异常是 RuntimeException
或其子类的实例时。(默认情况下,Error
实例也会导致回滚)。
默认配置还提供了对 Vavr 的 Try
方法的支持,以便在其返回“Failure”时触发事务回滚。这允许您使用 Try 处理函数式风格的错误,并在发生故障时自动回滚事务。有关 Vavr 的 Try 的更多信息,请参阅 Vavr 官方文档。以下是如何在事务方法中使用 Vavr 的 Try 的示例:
- 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
处理自动调整为对代理调用的实际异步句柄),更倾向于在返回的句柄中暴露异常,而不是重新抛出异常:
- Java
@Transactional @Async
public CompletableFuture<String> myTransactionalMethod() {
try {
return CompletableFuture.completedFuture(delegate.myDataAccessOperation());
}
catch (DataAccessException ex) {
return CompletableFuture.failedFuture(ex);
}
}
在默认配置中,从事务方法中抛出的已检查异常(checked exceptions)不会导致事务回滚。你可以通过指定回滚规则(rollback rules)来精确配置哪些 Exception
类型会标记事务为回滚,包括已检查异常。
回滚规则
回滚规则决定了当抛出特定异常时,事务是否应该回滚,这些规则基于异常类型或异常模式。
回滚规则可以通过 XML 中的 rollback-for
和 no-rollback-for
属性进行配置,这些属性允许将规则定义为模式。当使用 @Transactional 时,回滚规则可以通过 rollbackFor
/noRollbackFor
和 rollbackForClassName
/noRollbackForClassName
属性进行配置,这些属性分别允许基于异常类型或模式定义规则。
当使用异常类型定义回滚规则时,该类型将用于匹配抛出的异常类型及其超类型,从而提供类型安全性,并避免在使用模式时可能发生的意外匹配。例如,jakarta.servlet.ServletException.class
的值将仅匹配 jakarta.servlet.ServletException
类型及其子类的异常。
当使用异常模式定义回滚规则时,模式可以是异常类型的完全限定类名或完全限定类名的子字符串(必须是 Throwable
的子类),目前不支持通配符。例如,"jakarta.servlet.ServletException"
或 "ServletException"
的值将匹配 jakarta.servlet.ServletException
及其子类。
你必须仔细考虑模式的特定性以及是否包含包信息(这不是强制性的)。例如,"Exception"
将匹配几乎所有内容,并且可能会隐藏其他规则。如果 "Exception"
旨在为所有受检异常定义规则,那么 "java.lang.Exception"
将是正确的。对于更独特的异常名称,如 "BaseBusinessException"
,可能不需要使用完全限定的类名作为异常模式。
此外,基于模式的回滚规则可能会导致对类似命名的异常和嵌套类的意外匹配。这是因为,如果抛出的异常的名称包含为回滚规则配置的异常模式,则抛出的异常将被视为与基于模式的回滚规则匹配。例如,给定一个配置为匹配 "com.example.CustomException"
的规则,该规则将匹配名为 com.example.CustomExceptionV2
的异常(与 CustomException
在同一个包中但带有额外后缀的异常)或名为 com.example.CustomException$AnotherException
的异常(在 CustomException
中声明为嵌套类的异常)。
:::
以下 XML 片段展示了如何通过 rollback-for
属性提供异常模式来配置针对已检查的、特定于应用程序的 Exception
类型的回滚:
<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 框架的事务基础设施,即使面对未处理的 InstrumentNotFoundException
,也要提交相关事务:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
当 Spring 框架的事务基础设施捕获异常并参考配置的回滚规则以确定是否标记事务进行回滚时,最强匹配的规则将生效。因此,在以下配置的情况下,除了 InstrumentNotFoundException
之外的任何异常都会导致相关事务回滚:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
</tx:attributes>
</tx:advice>
你也可以通过编程方式指示需要回滚。虽然简单,但这个过程相当侵入性,并且将你的代码与 Spring 框架的事务基础设施紧密耦合。以下示例展示了如何通过编程方式指示需要回滚:
- 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 的简洁架构背道而驰。