Spring Boot中Propagation.REQUIRED事务异常未回滚问题求助
问题根因
Spring 声明式事务基于 AOP 动态代理实现,只有事务方法被代理对象调用时,@Transactional 注解才会生效。你当前代码存在的核心问题是同类内部方法自调用:
- 入口
run方法直接调用本类的insertUserBunty,没有走代理对象,该方法上的@Transactional注解完全不生效,没有开启全局事务 - 后续
insertUserBunty调用本类的insertUserKumar同样是本地方法调用,该方法的事务注解也不会生效
相当于两个save操作都运行在无事务环境下,每个持久化操作执行后就自动提交,异常抛出时两个写入操作已经完成提交,自然不会回滚,这也是你加/不加@EnableTransactionManagement都不生效的根本原因。
解决方案
下面是三种常用的修复方式,按需选择即可:
方案1:拆分事务方法到独立Bean
将insertUserBunty、insertUserKumar抽到单独的事务Bean中,在AppRunner中注入该Bean调用方法,调用链路走代理即可触发事务切面逻辑。
示例代码:
// 单独抽离的事务服务类 @Service @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; @Transactional(propagation = Propagation.REQUIRED) public void insertUserBunty() { User bunty = new User(null, "Bunty"); userRepository.save(bunty); insertUserKumar(); } @Transactional(propagation = Propagation.REQUIRED) public void insertUserKumar() { User kumar = new User(null, "Kumar"); userRepository.save(kumar); throw new RuntimeException("This will rollback both insert Bunty and Kumar"); } } // 修改AppRunner @Slf4j @Component @RequiredArgsConstructor public class AppRunner implements CommandLineRunner { private final UserService userService; @Override public void run(String... args) throws Exception { userService.insertUserBunty(); } }
方案2:注入自身代理对象
在当前类中注入自身的代理对象,通过代理对象调用事务方法,注意添加@Lazy注解避免循环依赖问题,同时需要把事务方法改为public权限:
@Slf4j @Component @RequiredArgsConstructor public class AppRunner implements CommandLineRunner { private final UserRepository userRepository; @Lazy @Autowired private AppRunner self; @Override public void run(String... args) throws Exception { self.insertUserBunty(); } @Transactional(propagation = Propagation.REQUIRED) public void insertUserBunty() { User bunty = new User(null, "Bunty"); userRepository.save(bunty); self.insertUserKumar(); } @Transactional(propagation = Propagation.REQUIRED) public void insertUserKumar() { User kumar = new User(null, "Kumar"); userRepository.save(kumar); throw new RuntimeException("This will rollback both insert Bunty and Kumar"); } }
方案3:使用编程式事务
直接使用TransactionTemplate手动控制事务边界,无需依赖代理机制:
@Slf4j @Component @RequiredArgsConstructor public class AppRunner implements CommandLineRunner { private final UserRepository userRepository; private final TransactionTemplate transactionTemplate; @Override public void run(String... args) throws Exception { transactionTemplate.execute(status -> { User bunty = new User(null, "Bunty"); userRepository.save(bunty); User kumar = new User(null, "Kumar"); userRepository.save(kumar); throw new RuntimeException("This will rollback both insert Bunty and Kumar"); }); } }
额外注意事项
- 事务方法尽量设置为
public权限,非public方法在部分代理模式下会导致注解失效 - 不要在事务方法内部捕获异常不抛出,事务默认只会在遇到未捕获的
RuntimeException和Error时回滚,如果需要捕获异常请手动设置回滚标记,或者修改rollbackFor属性指定回滚异常类型
内容的提问来源于stack exchange,提问作者Bal Vikash Sharma
相关产品推荐
相关产品推荐

