事务碰到事务,听谁的
方法 A(有事务)调用方法 B(也有 @Transactional),B 的事务是加入 A,还是另起炉灶?传播行为就是回答这个问题的。七种里,真正高频的是三式,其余四种了解即可。
三大主力对比
REQUIRED(默认) 有事务就加入,没有就新开
→ 内外同一个事务,任何一方回滚,全体陪葬
REQUIRES_NEW 挂起外层事务,开一个全新的
→ 两个独立事务:内层回滚不影响外层,
外层回滚也带不走内层已提交的数据
NESTED 在当前事务里设 Savepoint 存档点
→ 内层回滚退回存档点,外层照常;
但外层若回滚,内层跟着没(它是存档,不是独立事务)一句话记忆:REQUIRED 是绑在一起,REQUIRES_NEW 是各过各的,NESTED 是游戏存档。
经典场景:审计日志必须独立落库
业务操作失败要回滚,但「谁在什么时候做了失败操作」的日志必须留下来——日志方法标 REQUIRES_NEW:外层业务回滚,内层日志已独立提交,带不走。反过来,批量导入要「整批可放弃、单条可跳过」——单条处理套 NESTED,坏一条回滚到存档点继续下一条,整批的去留由外层统一裁决。注意 NESTED 依赖 JDBC Savepoint,JPA 体系不支持。
两笔成本,心里有数
其一,连接占用:REQUIRES_NEW 挂起外层时另拿一条连接,内外并行占用两条;嵌套一多或连接池偏小,池被借空直接卡死。其二,锁冲突死锁:外层事务改了某行还没提交(行锁在手),内层 REQUIRES_NEW 再去改同一行——自己等自己,经典死锁。看到「事务方法内嵌 REQUIRES_NEW 且操作同一批数据」,先想锁。
其余四式速览
SUPPORTS——有事务就加入,没有就非事务跑;NOT_SUPPORTED——挂起当前事务,非事务执行;MANDATORY——必须有外层事务,否则抛异常(保护性声明);NEVER——不许有事务,有就抛异常。后四种常见于框架代码做约束声明。
实现彩蛋:挂起就是连接换手
挂起在源码里的样子:把外层的 ConnectionHolder 从 ThreadLocal 解绑、封进 SuspendedResourcesHolder 存起来,内层新拿连接绑定;内层结束后解绑、再把外层的绑回去。传播行为切换的本质,就是 ThreadLocal 上的连接换手。至此事务三篇闭环:原理(第 8 篇)→ 失效(第 9 篇)→ 嵌套(本篇)。
下一篇转场注入体系:@Autowired 与 @Resource 的门道,以及集合注入为什么能一次拿到所有实现。
评论 (0)