Spring根事务回滚时嵌套事务未同步回滚的解决方法
你当前配置的Propagation.REQUIRES_NEW传播行为本身就不会跟随外层事务回滚:该传播级别的语义是执行到目标方法时,直接挂起当前外层事务,创建一个完全独立的新事务(持有独立的数据库连接),方法执行完成后会立刻独立提交事务。这个事务和外层根事务没有从属关系,外层事务后续无论是提交还是回滚,都无法影响已经提交完成的REQUIRES_NEW事务的变更,这就是你能查到saveItem落库数据的直接原因。
除此之外你贴的代码还有两个会影响事务行为的问题:
BusinessService#doSmth中用try-catch吞掉了businessServiceHelper调用的所有异常,就算内层操作抛出异常,也无法传递到外层事务切面,外层事务根本感知不到执行错误- Spring声明式事务基于AOP代理实现,同类内部调用、非public方法上的
@Transactional注解不会生效,你注释中标注的开新事务的process方法如果是同类内部调用,事务配置本身就不会生效。
根据你的业务场景二选一即可:
方案1:使用默认REQUIRED传播级别(通用场景推荐)
将BusinessServiceHelper#saveItem的事务传播级别改为默认的Propagation.REQUIRED,去掉REQUIRES_NEW配置:
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; public class BusinessServiceHelper { // 直接加入外层已存在的根事务,共用同一个事务上下文 @Transactional(propagation = Propagation.REQUIRED) public void saveItem(Item item) { updateItem(item); saveItem(item); } }
该配置下所有内层数据库操作都会加入PaymentProcessor#process开启的根事务,所有操作属于同一个事务单元,当paymentService.pay()抛出异常触发根事务回滚时,saveItem的所有变更会同步回滚。
注意需要调整BusinessService#doSmth中吞异常的逻辑:如果捕获到需要触发回滚的异常,要么向上抛出,要么通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()手动标记事务回滚,否则异常无法传递到事务切面,根事务不会触发回滚。
方案2:使用NESTED嵌套事务(需要部分回滚能力时选用)
如果你需要保留「单个item处理失败时只回滚当前item的操作、不影响循环内其他item执行,但外层根事务整体回滚时所有item操作同步回滚」的能力,就使用Propagation.NESTED传播级别:
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; public class BusinessServiceHelper { // 基于外层事务创建保存点的嵌套事务 @Transactional(propagation = Propagation.NESTED) public void saveItem(Item item) { updateItem(item); saveItem(item); } }
该传播级别基于数据库保存点(savepoint)实现,行为逻辑如下:
- 单个
saveItem执行抛出异常时,只会回滚到该方法执行前创建的保存点,不会回滚整个外层事务,循环内其他item的处理不受影响 - 外层根事务最终抛出异常触发整体回滚时,所有嵌套事务的操作会跟随根事务一同回滚,不会出现数据残留
注意该传播级别需要JDBC驱动和数据库支持保存点机制,主流关系型数据库(MySQL InnoDB、PostgreSQL等)的新版本驱动均支持该特性。
不要混淆三种传播级别的核心差异:
REQUIRED:默认级别,加入当前存在的事务,没有就新建事务,所有操作属于同一个事务单元,要么一起提交要么一起回滚REQUIRES_NEW:永远新建独立事务,和外层事务完全隔离,自身提交/回滚不受外层影响,外层回滚也不会影响已提交的独立事务NESTED:外层事务的子事务,基于保存点实现,子事务回滚不影响外层,外层回滚子事务必回滚
内容的提问来源于stack exchange,提问作者Michael

