📌 阅读前提示:第 25 篇看到
TransactionInterceptor在业务方法前后管理事务。本篇深入事务最核心、也最容易考糊的概念——传播行为(Propagation)。当你在一个事务方法里调用另一个事务方法,Spring 怎么决定「新建事务」还是「加入已有事务」?答案在AbstractPlatformTransactionManager.getTransaction。
一、引子:为什么需要传播行为
现实中常出现「外层方法开事务,内层方法也标了 @Transactional」的场景。内层是复用外层事务、自己新开一个,还是干脆不事务?这就是传播行为要回答的。
@Transactional(propagation=Propagation.REQUIRED)// 默认publicvoidouter(){inner();}@Transactional(propagation=Propagation.REQUIRES_NEW)publicvoidinner(){/* ... */}二、七种传播行为一览
| 行为 | 含义 |
|---|---|
REQUIRED(默认) | 有就加入,没有就新建 |
REQUIRES_NEW | 挂起当前事务,新建一个独立事务 |
NESTED | 在当前事务内开嵌套保存点(仅 JDBC 支持) |
SUPPORTS | 有事务就加入,没有就以非事务执行 |
NOT_SUPPORTED | 挂起当前事务,以非事务执行 |
NEVER | 必须在非事务环境,否则抛异常 |
MANDATORY | 必须已有事务,否则抛异常 |
三、源码追踪:getTransaction 的决策
// AbstractPlatformTransactionManager.java@OverridepublicfinalTransactionStatusgetTransaction(@NullableTransactionDefinitiondefinition){// ① 已存在事务?if(isExistingTransaction(transaction)){returnhandleExistingTransaction(definition,transaction,debugEnabled);// ★ 已有事务分支}// ② 无事务时,按定义处理if(definition.getPropagationBehavior()==TransactionDefinition.PROPAGATION_MANDATORY){thrownewIllegalTransactionStateException("无现有事务,MANDATORY 不允许");}elseif(definition.getPropagationBehavior()==TransactionDefinition.PROPAGATION_REQUIRED||definition.getPropagationBehavior()==TransactionDefinition.PROPAGATION_REQUIRES_NEW||definition.getPropagationBehavior()==TransactionDefinition.PROPAGATION_NESTED){// 挂起外层 → 新建事务SuspendedResourcesHoldersuspendedResources=suspend(null);returnstartTransaction(definition,transaction,debugEnabled,suspendedResources);}else{// SUPPORTS / NOT_SUPPORTED / NEVER → 非事务执行returnprepareTransactionStatus(definition,null,true,newSynchronization,debugEnabled,null);}}3.1 已有事务时的关键分支
privateTransactionStatushandleExistingTransaction(TransactionDefinitiondefinition,Objecttransaction,booleandebugEnabled){intpropagation=definition.getPropagationBehavior();if(propagation==PROPAGATION_NEVER){thrownewIllegalTransactionStateException("已存在事务,NEVER 不允许");}if(propagation==PROPAGATION_NOT_SUPPORTED){Objectsuspended=suspend(transaction);// 挂起现有事务returnprepareTransactionStatus(...非事务...);}if(propagation==PROPAGATION_REQUIRES_NEW){Objectsuspended=suspend(transaction);// ★ 挂起外层returnstartTransaction(definition,transaction,debugEnabled,suspended);// ★ 全新事务}if(propagation==PROPAGATION_NESTED){if(useSavepointForNestedTransaction()){DefaultTransactionStatusstatus=prepareTransactionStatus(...);status.createAndHoldSavepoint();// ★ 创建保存点(嵌套)returnstatus;}}// PROPAGATION_REQUIRED / SUPPORTS / (默认) → 加入现有事务returnprepareTransactionStatus(definition,transaction,false,newSynchronization,debugEnabled,null);}📌 结论:
REQUIRED加入现有事务(共享连接)、REQUIRES_NEW挂起外层开新事务(独立提交/回滚)、NESTED用保存点(外层回滚则内层也回滚,内层回滚不影响外层)。
四、三种典型行为对比
| 行为 | 与外层关系 | 内层异常影响外层? | 外层异常影响内层? |
|---|---|---|---|
REQUIRED | 同一事务 | 是(一起回滚) | 是 |
REQUIRES_NEW | 完全独立 | 否 | 否 |
NESTED | 同一连接 + 保存点 | 是(内层回滚不波及外层) | 是 |
⚠️ REQUIRES_NEW vs NESTED:
REQUIRES_NEW是两个物理事务,互不影响;NESTED是同一物理事务内的保存点,外层提交内层才生效,外层回滚内层必回滚。
五、事务传播行为决策图
六、常见误区
| 误区 | 正解 |
|---|---|
| REQUIRED 每次都新建事务 | 不,有就加入,无才新建 |
| REQUIRES_NEW 和 NESTED 一样 | 否,前者独立物理事务,后者同一连接保存点 |
| NESTED 所有数据源都支持 | 仅 JDBC 支持保存点,JTA 不支持 |
| 内层回滚一定导致外层回滚 | 仅 REQUIRED/NESTED;REQUIRES_NEW 不会 |
| 传播行为在提交时判定 | 不,在getTransaction进入时就已决策 |
🧪 面试题自测
- 七种传播行为中,哪些会新建事务、哪些加入、哪些非事务?
REQUIRED与REQUIRES_NEW的本质区别?NESTED的保存点机制是什么?有何限制?getTransaction如何判断「已有事务」?MANDATORY和NEVER分别在什么情况下抛异常?- 内层
REQUIRES_NEW异常,外层REQUIRED会回滚吗?
🔧 Debug 小技巧
在AbstractPlatformTransactionManager.getTransaction断点,分别用REQUIRED调REQUIRED、REQUIRED调REQUIRES_NEW两种场景,观察isExistingTransaction返回值与handleExistingTransaction走哪个分支、newTransaction标记真假。
下一篇预告
第 27 篇:@Transactional的 7 大失效场景与源码归因——为什么你明明加了注解事务却不回滚?自调用、非 public、异常类型不对、多线程……逐一用源码定位。
如果这篇对你有帮助,欢迎点赞 · 收藏 · 关注三连支持。
Spring 源码系列共 30 篇,由浅入深持续更新中。有疑问或想深挖的源码点,评论区告诉我,下篇见。