回滚声明式事务

前一节概述了如何在应用程序中为类(通常是服务层类)声明式地指定事务设置的基础知识。本节描述了如何在XML配置中以简单的声明方式控制事务的回滚。有关使用@Transactional注解声明式控制回滚语义的详细信息,请参阅@Transactional设置

向Spring Framework的事务基础设施指示事务的工作应该回滚的推荐方式是从当前在事务上下文中执行的代码中抛出一个Exception。Spring Framework的事务基础设施代码捕获任何未处理的Exception,当它沿着调用堆栈向上冒泡时,确定是否标记事务以进行回滚。

在其默认配置中,Spring Framework的事务基础设施代码仅在运行时出现未检查的异常的情况下标记事务以进行回滚。也就是说,当抛出的异常是RuntimeException的实例或子类时(Error实例也默认导致回滚)。

从Spring Framework 5.2开始,默认配置还提供了对Vavr的Try方法的支持,当它返回“Failure”时触发事务回滚。这使您可以使用Try处理函数式错误,并在发生失败时自动回滚事务。有关Vavr的Try的更多信息,请参阅[官方Vavr文档](www.vavr.io/vavr-docs/#_try)。

以下是如何在事务方法中使用Vavr的Try的示例:

  • Java

@Transactional
public Try<String> myTransactionalMethod() {
	// 如果myDataAccessOperation抛出异常,它将被Try.of()创建的Try实例捕获,并包装在Failure类中,可以使用Try实例上的isFailure()方法进行检查。
	return Try.of(delegate::myDataAccessOperation);
}

从事务方法中抛出的已检查异常在默认配置中不会导致回滚。您可以通过指定回滚规则来精确配置哪些Exception类型会标记事务以进行回滚,包括已检查的异常。

回滚规则

回滚规则确定在抛出给定异常时是否应回滚事务,规则基于异常类型或异常模式。

可以通过rollback-forno-rollback-for属性在XML中配置回滚规则,这允许将规则定义为模式。当使用@Transactional时,可以通过rollbackFor/noRollbackForrollbackForClassName/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 Framework的事务基础设施即使在出现未处理的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之外的任何异常都会导致相关事务回滚:

<tx:advice id="txAdvice">
	<tx:attributes>
		<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
	</tx:attributes>
</tx:advice>

您还可以通过编程方式指示必须回滚。尽管简单,但这个过程非常侵入性,并且将您的代码与Spring Framework的事务基础设施紧密耦合。以下示例展示了如何通过编程方式指示必须回滚:

  • Java

  • Kotlin

public void resolvePosition() {
	try {
		// 一些业务逻辑...
	} catch (NoProductInStockException ex) {
		// 通过编程方式触发回滚
		TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
	}
}
fun resolvePosition() {
	try {
		// 一些业务逻辑...
	} catch (ex: NoProductInStockException) {
		// 通过编程方式触发回滚
		TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
	}
}

强烈建议您尽可能使用声明式回滚的方法。如果绝对需要,可以使用编程式回滚,但其使用与实现干净的POJO架构相悖。