同类方法调用中事务仍生效?Spring Boot事务异常排查
Spring事务内部调用异常导致全量回滚问题分析
查阅了大量相关资料,均提到同类内部方法调用时子方法无事务。按此逻辑,执行下方代码后数据库应存在method2、无method1,但实际两者均未保存。环境:Spring Boot 3.3.1,Transactional默认代理模式。
问题代码
@Service public class MyService { @Transactional public void add(AddInventoryChangeDto dto) { InventoryChange c = new InventoryChange(); c.setName("method1"); c.setCreateUserId("95a9d193354d19b833408d1c486f79c3"); inventoryChangeMapper.insert(c); inStock(); } private void inStock() { InventoryChange c = new InventoryChange(); c.setName("method2"); c.setCreateUserId("95a9d193354d19b833408d1c486f79c3"); inventoryChangeMapper.insert(c); throw new RuntimeException("hh"); } } @RestController public class MyController { @Autowired private MyService service; @PostMapping("/test") public void test(@RequestBody AddInventoryChangeDto dto) { service.add(dto); } }
原因分析
- 核心误区:混淆了「子方法无独立事务」和「子方法脱离父方法事务上下文」的概念。
add方法标记了@Transactional,整个方法处于一个Spring管理的事务中。内部调用的inStock是private方法,Spring动态代理无法拦截该调用,因此inStock确实没有自己的独立事务,但它完全运行在add方法的事务上下文内。RuntimeException是Spring默认触发事务回滚的异常类型,当inStock抛出该异常时,Spring会将整个add方法的事务标记为回滚状态,最终两个insert操作都会被回滚,数据库中不会留存任何记录。
实现预期效果的方案
如果需要让inStock的操作不受add方法事务回滚影响,即保存method2、回滚method1,可以采用以下两种方式:
方案1:抽离子方法到独立Service
将inStock方法移至另一个Spring管理的Service类,给该方法标记@Transactional(propagation = Propagation.REQUIRES_NEW)以强制开启新事务:
@Service public class StockService { @Autowired private InventoryChangeMapper inventoryChangeMapper; @Transactional(propagation = Propagation.REQUIRES_NEW) public void inStock() { InventoryChange c = new InventoryChange(); c.setName("method2"); c.setCreateUserId("95a9d193354d19b833408d1c486f79c3"); inventoryChangeMapper.insert(c); throw new RuntimeException("hh"); } } @Service public class MyService { @Autowired private StockService stockService; @Transactional public void add(AddInventoryChangeDto dto) { InventoryChange c = new InventoryChange(); c.setName("method1"); c.setCreateUserId("95a9d193354d19b833408d1c486f79c3"); inventoryChangeMapper.insert(c); stockService.inStock(); } }
方案2:通过自身代理对象调用
在当前Service中注入ApplicationContext,获取自身的代理对象,将inStock改为public方法并标记@Transactional(propagation = Propagation.REQUIRES_NEW):
@Service public class MyService { @Autowired private ApplicationContext context; @Autowired private InventoryChangeMapper inventoryChangeMapper; @Transactional public void add(AddInventoryChangeDto dto) { InventoryChange c = new InventoryChange(); c.setName("method1"); c.setCreateUserId("95a9d193354d19b833408d1c486f79c3"); inventoryChangeMapper.insert(c); // 通过代理对象调用inStock,触发新事务 context.getBean(MyService.class).inStock(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void inStock() { InventoryChange c = new InventoryChange(); c.setName("method2"); c.setCreateUserId("95a9d193354d19b833408d1c486f79c3"); inventoryChangeMapper.insert(c); throw new RuntimeException("hh"); } }
以上两种方案中,inStock的新事务会独立提交,而add的事务会因异常回滚,最终数据库中只会存在method2的记录,符合最初的预期。
内容的提问来源于stack exchange,提问作者user1434702
相关产品推荐
相关产品推荐

