原文来自于:zha-ge.cn/java/102
事务嵌套场景必问:Spring 传播机制如何真正发挥作用?
说起来,有谁没被Spring的事务传播坑过?春风吹又生的坑------面试一问到"REQUIRES_NEW和NESTED到底哪个'真新建',谁能真正做到子事务独立提交回滚",九成人都变成了表情包: "啊?不是加@Transactional就行了么?"
其实,要不是我去年踩了个"史诗级大雷",我其实也不太敢唧唧歪歪聊这个。下面听我给各位讲个"事务穿越记"。
有一次,产品经理喜提个业务:主流程成功就插日志,日志不插也不能影响主流程。我的第一个反应------这还不简单?日志
方法加个@Transactional(propagation = Propagation.REQUIRES_NEW)
,高高兴兴写代码:
java
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAction(String msg) {
// 记录日志
// ...
// 人为制造异常,验证是否独立
if (msg.contains("fail")) throw new RuntimeException("日志写入失败");
}
}
主业务大哥自然地调用 logService.logAction()
,想着即使记录日志异常抛了,主流程无所谓。
结果上线一测,这主流程特么居然全回滚了!?脸都绿了。我的先入为主的小算盘------"REQUIRES_NEW自动生成新事务,和外面事务无关",一下给打脸。
踩坑瞬间
真相其实离谱得很,Spring事务传播=面试题,实践全靠玄学踩坑:
- 内部方法直接调用,事务传播不起效。你以为开了新事务,其实屁都没发生!
- 没仔细配置事务管理器,REQUIRES_NEW/NESTED压根没新世界可开。
- 忘了
try-catch
,异常没吃下来,外层事务直接遭殃。
我的bug现场:
java
@Service
public class MainService {
@Transactional
public void mainJob(String in) {
// ...
logService.logAction("fail log"); // 日志抛异常
// ...
}
}
控制台异常一大片,数据库里啥都没,主业务说"锅你背"。 为什么?
- 自己类里直接调用方法,其实Spring的代理根本没接管到你那层!
- @Transactional其实只对通过Spring代理调用的新事务管用,self调用和普通调用→全白忙活。
经验启示
这场"看似很会,实际踩坑"的事务体验,直接送我几条血的教训:
- 切记自调用不走Spring代理,传播属性全靠缘分。不信→看官方文档。
REQUIRES_NEW
真的要跨service注入调用 才有用,自己调自己=白忙活。- 对,比如@Autowired的service,不是this.XX方法。
- 外部异常记得try-catch,不然后续流程一锅端!
NESTED
要求底层是支持savepoint的数据库和DataSource,不是所有场景肤浅理解。- 面试时如果他追问,就拿"实际开发要看AOP代理切入点、自调用无效、异常catch防止事务传染"这些说出来,气场升天。
再多说一句:"事务传播机制只有你真正把它们玩出事一两次后,才能发自内心懂。"
最后总结下
事务这玩意,看着就几个注解属性,真到生产环境里就是个大型翻车现场。
传播机制的几个关键点,面试和实战都能用得上: REQUIRED:默认选手,外有就加入,没就开新。 REQUIRES_NEW:真·新开事务,但必须通过 Spring 代理调用,异常别忘了 catch。 NESTED:玩 savepoint 的,支持局部回滚,前提是底层数据库和事务管理器撑得住。 SUPPORTS / MANDATORY / NEVER:这仨平时少用,但面试官爱问,知道就好。
一句话总结:传播属性本质上是给"事务边界"立规矩,但要生效得满足两个条件: 1.调用路径必须走 Spring 代理(自调用白搭)。 2.事务管理器和数据库要真支持(不然就是纸上谈兵)。