一个 if-else 瀑布定乾坤:一次读透 Spring 事务传播行为与挂起恢复的源码实战
上一篇(《读透 Spring 声明式事务与 Druid、HikariCP 介入点》)把
TransactionInterceptor#invoke到doBegin的主干走通了,结尾留了两个坑:七种传播行为到底在哪儿分派、@EnableTransactionManagement怎么把代理织进 Bean。这一篇补齐。读之前我对传播行为的认知是背诵版的表格------REQUIRED有就加入、REQUIRES_NEW挂起新建、NESTED保存点......读完后认知被刷新成一句话:七种传播行为没有任何魔法,全部摊在一个两层 if-else 瀑布里;所谓"挂起",就是把 ThreadLocal 里的连接摘下来,装进一个叫SuspendedResourcesHolder的盒子。顺便,上一篇那个UnexpectedRollbackException的精确抛出位置,这次也钉到了具体的一行。
S --- Situation:能背七种传播行为,画不出一张分派图
上一篇结束时链路走到 tm.getTransaction(txAttr) 就停了------拦截器拿到 TransactionStatus 后直接执行业务。传播行为的分派、事务的挂起/恢复、NESTED 的"局部回滚",全部发生在这行调用的内部。这一块有几个长期靠背诵支撑的知识点:
- 七种传播行为的表格人人会背,但"挂起"物理上做了什么、"恢复"又恢复了什么,说不出具体代码;
NESTED和REQUIRES_NEW都能"独立回滚",区别背得出(保存点 vs 新连接),但代码上走到哪个岔路口分家,没见过;REQUIRED内层异常"只标记不回滚" 的机制上一篇讲过结论(rollback-only),但标记之后外层 commit 时是在哪一行、以什么路径转成UnexpectedRollbackException的,还欠一个精确坐标;@EnableTransactionManagement就一个注解 ,它是怎么让所有带@Transactional的 Bean 变成代理的?注册了哪些基础设施 Bean?
后四个问题共同指向两块代码:AbstractPlatformTransactionManager(分派与挂起)和 EnableTransactionManagement 的注册链路。
T --- Task:三个目标,一个约束
- 画全分派图 :把
getTransaction内部七种传播行为 × 有/无事务两种前提共 11 条路径,整理成一张能对照源码行回溯的分派图; - 拆穿两个"黑盒" :挂起/恢复的物理动作到底是什么;
NESTED与REQUIRES_NEW在代码上的分家点在哪; - 补齐注册链路 :从
@EnableTransactionManagement到代理生成,说清注册了哪三个 Bean、代理是谁在什么时机创建的。
约束:延续上一篇的标准------所有结论必须能回溯到源码的具体方法;源码基线仍为 Spring 5.x(spring-tx / spring-jdbc / spring-context)。
A --- Action:一个两层瀑布、一个盒子、一条岔路
第一步:分派的总入口,只是"有没有事务"的一刀
getTransaction 开头三行就完成了总分类,后面所有分派都建立在这一刀之上:
java
Object transaction = doGetTransaction(); // 从事务对象里拿到当前线程绑定的连接(可能为空)
if (isExistingTransaction(transaction)) { // 有事务(连接存在且事务已激活)?
return handleExistingTransaction(...); // → 走"有事务"瀑布
}
// 没有 → MANDATORY 抛异常 / REQUIRED·REQUIRES_NEW·NESTED 新建 / 其余空事务
AbstractPlatformTransactionManager.java#getTransaction 骨架------doGetTransaction 返回的 transaction 对象里,连接最初是空的,doBegin 才会真正借连接。
关键认知:"存在事务"的判断依据是物理的------ThreadLocal 里绑着连接且 transactionActive=true,不是什么抽象计数。这一刀切完,两边各是一个小瀑布,分成三张图看:
图 0 · 总入口------就一刀:
#mermaid-svg-NBklU39wyGPOei8b{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NBklU39wyGPOei8b .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NBklU39wyGPOei8b .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NBklU39wyGPOei8b .error-icon{fill:#552222;}#mermaid-svg-NBklU39wyGPOei8b .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NBklU39wyGPOei8b .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NBklU39wyGPOei8b .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NBklU39wyGPOei8b .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NBklU39wyGPOei8b .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NBklU39wyGPOei8b .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NBklU39wyGPOei8b .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NBklU39wyGPOei8b .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NBklU39wyGPOei8b .marker.cross{stroke:#333333;}#mermaid-svg-NBklU39wyGPOei8b svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NBklU39wyGPOei8b p{margin:0;}#mermaid-svg-NBklU39wyGPOei8b .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NBklU39wyGPOei8b .cluster-label text{fill:#333;}#mermaid-svg-NBklU39wyGPOei8b .cluster-label span{color:#333;}#mermaid-svg-NBklU39wyGPOei8b .cluster-label span p{background-color:transparent;}#mermaid-svg-NBklU39wyGPOei8b .label text,#mermaid-svg-NBklU39wyGPOei8b span{fill:#333;color:#333;}#mermaid-svg-NBklU39wyGPOei8b .node rect,#mermaid-svg-NBklU39wyGPOei8b .node circle,#mermaid-svg-NBklU39wyGPOei8b .node ellipse,#mermaid-svg-NBklU39wyGPOei8b .node polygon,#mermaid-svg-NBklU39wyGPOei8b .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NBklU39wyGPOei8b .rough-node .label text,#mermaid-svg-NBklU39wyGPOei8b .node .label text,#mermaid-svg-NBklU39wyGPOei8b .image-shape .label,#mermaid-svg-NBklU39wyGPOei8b .icon-shape .label{text-anchor:middle;}#mermaid-svg-NBklU39wyGPOei8b .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NBklU39wyGPOei8b .rough-node .label,#mermaid-svg-NBklU39wyGPOei8b .node .label,#mermaid-svg-NBklU39wyGPOei8b .image-shape .label,#mermaid-svg-NBklU39wyGPOei8b .icon-shape .label{text-align:center;}#mermaid-svg-NBklU39wyGPOei8b .node.clickable{cursor:pointer;}#mermaid-svg-NBklU39wyGPOei8b .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NBklU39wyGPOei8b .arrowheadPath{fill:#333333;}#mermaid-svg-NBklU39wyGPOei8b .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NBklU39wyGPOei8b .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NBklU39wyGPOei8b .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NBklU39wyGPOei8b .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NBklU39wyGPOei8b .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NBklU39wyGPOei8b .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NBklU39wyGPOei8b .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NBklU39wyGPOei8b .cluster text{fill:#333;}#mermaid-svg-NBklU39wyGPOei8b .cluster span{color:#333;}#mermaid-svg-NBklU39wyGPOei8b div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NBklU39wyGPOei8b .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NBklU39wyGPOei8b rect.text{fill:none;stroke-width:0;}#mermaid-svg-NBklU39wyGPOei8b .icon-shape,#mermaid-svg-NBklU39wyGPOei8b .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NBklU39wyGPOei8b .icon-shape p,#mermaid-svg-NBklU39wyGPOei8b .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NBklU39wyGPOei8b .icon-shape .label rect,#mermaid-svg-NBklU39wyGPOei8b .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NBklU39wyGPOei8b .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NBklU39wyGPOei8b .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NBklU39wyGPOei8b :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 有事务(ThreadLocal 绑着连接且激活)
无事务
getTransaction(definition)
isExistingTransaction()?
图AhandleExistingTransaction
图B无事务三分支
图 A · 已存在事务(handleExistingTransaction,428-503 行)------五个连续的 if:
#mermaid-svg-yC144vtyjVhUdrAF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yC144vtyjVhUdrAF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yC144vtyjVhUdrAF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yC144vtyjVhUdrAF .error-icon{fill:#552222;}#mermaid-svg-yC144vtyjVhUdrAF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yC144vtyjVhUdrAF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yC144vtyjVhUdrAF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yC144vtyjVhUdrAF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yC144vtyjVhUdrAF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yC144vtyjVhUdrAF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yC144vtyjVhUdrAF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yC144vtyjVhUdrAF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yC144vtyjVhUdrAF .marker.cross{stroke:#333333;}#mermaid-svg-yC144vtyjVhUdrAF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yC144vtyjVhUdrAF p{margin:0;}#mermaid-svg-yC144vtyjVhUdrAF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yC144vtyjVhUdrAF .cluster-label text{fill:#333;}#mermaid-svg-yC144vtyjVhUdrAF .cluster-label span{color:#333;}#mermaid-svg-yC144vtyjVhUdrAF .cluster-label span p{background-color:transparent;}#mermaid-svg-yC144vtyjVhUdrAF .label text,#mermaid-svg-yC144vtyjVhUdrAF span{fill:#333;color:#333;}#mermaid-svg-yC144vtyjVhUdrAF .node rect,#mermaid-svg-yC144vtyjVhUdrAF .node circle,#mermaid-svg-yC144vtyjVhUdrAF .node ellipse,#mermaid-svg-yC144vtyjVhUdrAF .node polygon,#mermaid-svg-yC144vtyjVhUdrAF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yC144vtyjVhUdrAF .rough-node .label text,#mermaid-svg-yC144vtyjVhUdrAF .node .label text,#mermaid-svg-yC144vtyjVhUdrAF .image-shape .label,#mermaid-svg-yC144vtyjVhUdrAF .icon-shape .label{text-anchor:middle;}#mermaid-svg-yC144vtyjVhUdrAF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yC144vtyjVhUdrAF .rough-node .label,#mermaid-svg-yC144vtyjVhUdrAF .node .label,#mermaid-svg-yC144vtyjVhUdrAF .image-shape .label,#mermaid-svg-yC144vtyjVhUdrAF .icon-shape .label{text-align:center;}#mermaid-svg-yC144vtyjVhUdrAF .node.clickable{cursor:pointer;}#mermaid-svg-yC144vtyjVhUdrAF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yC144vtyjVhUdrAF .arrowheadPath{fill:#333333;}#mermaid-svg-yC144vtyjVhUdrAF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yC144vtyjVhUdrAF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yC144vtyjVhUdrAF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yC144vtyjVhUdrAF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yC144vtyjVhUdrAF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yC144vtyjVhUdrAF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yC144vtyjVhUdrAF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yC144vtyjVhUdrAF .cluster text{fill:#333;}#mermaid-svg-yC144vtyjVhUdrAF .cluster span{color:#333;}#mermaid-svg-yC144vtyjVhUdrAF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yC144vtyjVhUdrAF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yC144vtyjVhUdrAF rect.text{fill:none;stroke-width:0;}#mermaid-svg-yC144vtyjVhUdrAF .icon-shape,#mermaid-svg-yC144vtyjVhUdrAF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yC144vtyjVhUdrAF .icon-shape p,#mermaid-svg-yC144vtyjVhUdrAF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yC144vtyjVhUdrAF .icon-shape .label rect,#mermaid-svg-yC144vtyjVhUdrAF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yC144vtyjVhUdrAF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yC144vtyjVhUdrAF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yC144vtyjVhUdrAF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} handleExistingTransaction
NEVER
NOT_SUPPORTED
REQUIRES_NEW
NESTED
REQUIRED / SUPPORTS / MANDATORY
✗ 抛 IllegalTransactionStateException
suspend 挂起,返回空事务(无事务、不参与 commit)
suspend 挂起外层+ doBegin 借新连接
当前连接打 savepoint(不换连接、不 doBegin)
直接加入当前事务(newTransaction=false)
图 B · 无事务(getTransaction 372-416 行)------三分支:
#mermaid-svg-6qnodMUQjskPBAK3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6qnodMUQjskPBAK3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6qnodMUQjskPBAK3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6qnodMUQjskPBAK3 .error-icon{fill:#552222;}#mermaid-svg-6qnodMUQjskPBAK3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6qnodMUQjskPBAK3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6qnodMUQjskPBAK3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6qnodMUQjskPBAK3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6qnodMUQjskPBAK3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6qnodMUQjskPBAK3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6qnodMUQjskPBAK3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6qnodMUQjskPBAK3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6qnodMUQjskPBAK3 .marker.cross{stroke:#333333;}#mermaid-svg-6qnodMUQjskPBAK3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6qnodMUQjskPBAK3 p{margin:0;}#mermaid-svg-6qnodMUQjskPBAK3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6qnodMUQjskPBAK3 .cluster-label text{fill:#333;}#mermaid-svg-6qnodMUQjskPBAK3 .cluster-label span{color:#333;}#mermaid-svg-6qnodMUQjskPBAK3 .cluster-label span p{background-color:transparent;}#mermaid-svg-6qnodMUQjskPBAK3 .label text,#mermaid-svg-6qnodMUQjskPBAK3 span{fill:#333;color:#333;}#mermaid-svg-6qnodMUQjskPBAK3 .node rect,#mermaid-svg-6qnodMUQjskPBAK3 .node circle,#mermaid-svg-6qnodMUQjskPBAK3 .node ellipse,#mermaid-svg-6qnodMUQjskPBAK3 .node polygon,#mermaid-svg-6qnodMUQjskPBAK3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6qnodMUQjskPBAK3 .rough-node .label text,#mermaid-svg-6qnodMUQjskPBAK3 .node .label text,#mermaid-svg-6qnodMUQjskPBAK3 .image-shape .label,#mermaid-svg-6qnodMUQjskPBAK3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-6qnodMUQjskPBAK3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6qnodMUQjskPBAK3 .rough-node .label,#mermaid-svg-6qnodMUQjskPBAK3 .node .label,#mermaid-svg-6qnodMUQjskPBAK3 .image-shape .label,#mermaid-svg-6qnodMUQjskPBAK3 .icon-shape .label{text-align:center;}#mermaid-svg-6qnodMUQjskPBAK3 .node.clickable{cursor:pointer;}#mermaid-svg-6qnodMUQjskPBAK3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6qnodMUQjskPBAK3 .arrowheadPath{fill:#333333;}#mermaid-svg-6qnodMUQjskPBAK3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6qnodMUQjskPBAK3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6qnodMUQjskPBAK3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6qnodMUQjskPBAK3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6qnodMUQjskPBAK3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6qnodMUQjskPBAK3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6qnodMUQjskPBAK3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6qnodMUQjskPBAK3 .cluster text{fill:#333;}#mermaid-svg-6qnodMUQjskPBAK3 .cluster span{color:#333;}#mermaid-svg-6qnodMUQjskPBAK3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6qnodMUQjskPBAK3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6qnodMUQjskPBAK3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-6qnodMUQjskPBAK3 .icon-shape,#mermaid-svg-6qnodMUQjskPBAK3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6qnodMUQjskPBAK3 .icon-shape p,#mermaid-svg-6qnodMUQjskPBAK3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6qnodMUQjskPBAK3 .icon-shape .label rect,#mermaid-svg-6qnodMUQjskPBAK3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6qnodMUQjskPBAK3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6qnodMUQjskPBAK3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6qnodMUQjskPBAK3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 当前无事务
MANDATORY
REQUIRED / REQUIRES_NEW / NESTED
SUPPORTS / NOT_SUPPORTED / NEVER
✗ 抛 IllegalTransactionStateException
suspend(null) + doBegin新建事务(借新连接)
空事务(仅激活同步、无实际事务)
三张图合计 11 条路径,对照源码:图 A 即 handleExistingTransaction 里五个 if 的顺序------NEVER 排最前直接抛、NOT_SUPPORTED 挂起返回空状态、REQUIRES_NEW 挂起后 doBegin、NESTED 特判 useSavepointForNestedTransaction()(默认 true)走保存点、剩下的直接"Participating in existing transaction"。配色沿用全文约定:红色抛异常、蓝色换连接、绿色 savepoint、黄色空事务。
两张对照表背了很多年,源码里其实只是 handleExistingTransaction 里五个连续的 if:NEVER 排最前直接抛、NOT_SUPPORTED 挂起返回空状态、REQUIRES_NEW 挂起后 doBegin、NESTED 特判 useSavepointForNestedTransaction()(默认 true)走保存点、剩下的 REQUIRED/SUPPORTS/MANDATORY 直接"Participating in existing transaction"。
⚠️ 踩坑一:NESTED 和 REQUIRES_NEW 的分家点,不在"回滚"而在"开事务"
两者回滚时行为不同(NESTED 只回滚到保存点),但那只是结果。代码上的分家点在开事务的一刻,只差一个调用:
java
// REQUIRES_NEW:挂起旧连接(从 ThreadLocal 解绑、装盒待还),doBegin 借新连接
SuspendedResourcesHolder suspendedResources = suspend(transaction);
DefaultTransactionStatus status = newTransactionStatus(..., suspendedResources);
doBegin(transaction, definition);
// NESTED:什么都不挂起,在当前连接上打一个 savepoint
DefaultTransactionStatus status = prepareTransactionStatus(definition, transaction, false, ...);
status.createAndHoldSavepoint(); // 内部就是 con.setSavepoint(...)
AbstractPlatformTransactionManager.java 节选精简------左边多了 suspend + doBegin,右边只有一行 savepoint。
REQUIRES_NEW = 换一条连接开新事务;NESTED = 同一条连接上打一个书签。这直接决定了回滚的物理范围:NESTED 的 processRollback 里命中 status.hasSavepoint() 分支,调 rollbackToHeldSavepoint()------只回到书签;而 commit 路径里 NESTED 正常结束只是 releaseHeldSavepoint()(把书签撕掉),根本没有数据库级 commit,commit 的权利始终在外层。
教训一句话:两者的本质差异别从"回滚语义"倒推,从"开事务时做了什么"正着看,一个 if 就讲完了。
第二步:挂起 = 摘下来装盒,恢复 = 开盒放回去
"挂起"这个翻译听起来像操作系统级的动作,实际物理动作朴素得多。suspend 先摘 TransactionSynchronizationManager 里的五个线程状态(同步回调、事务名、readOnly、隔离级别、active 标记),再把连接从 ThreadLocal 解绑:
java
// doSuspend 的实现(DataSourceTransactionManager):
txObject.setConnectionHolder(null); // 事务对象里置空
return TransactionSynchronizationManager.unbindResource(dataSource); // 从 ThreadLocal 摘下连接
// doResume 的实现:
TransactionSynchronizationManager.bindResource(dataSource, suspendedResources); // 原样绑回去
DataSourceTransactionManager.java#doSuspend/doResume 全文------摘连接一行、放回一行。
摘下来的所有东西(连接 + 五个线程状态)装进 SuspendedResourcesHolder 这个盒子,挂在内层 TransactionStatus 上;内层事务结束后 cleanupAfterCompletion 调 resume,把盒子里的东西原样放回 ThreadLocal,外层事务继续用原来的连接,仿佛无事发生。
内层 REQUIRES_NEW ThreadLocalDataSource→连接 外层事务(连接 C1) 内层 REQUIRES_NEW ThreadLocalDataSource→连接 外层事务(连接 C1) #mermaid-svg-qbs2TgtUvsyeEGoi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qbs2TgtUvsyeEGoi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qbs2TgtUvsyeEGoi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qbs2TgtUvsyeEGoi .error-icon{fill:#552222;}#mermaid-svg-qbs2TgtUvsyeEGoi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qbs2TgtUvsyeEGoi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qbs2TgtUvsyeEGoi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qbs2TgtUvsyeEGoi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qbs2TgtUvsyeEGoi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qbs2TgtUvsyeEGoi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qbs2TgtUvsyeEGoi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qbs2TgtUvsyeEGoi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qbs2TgtUvsyeEGoi .marker.cross{stroke:#333333;}#mermaid-svg-qbs2TgtUvsyeEGoi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qbs2TgtUvsyeEGoi p{margin:0;}#mermaid-svg-qbs2TgtUvsyeEGoi .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-qbs2TgtUvsyeEGoi text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-qbs2TgtUvsyeEGoi .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-qbs2TgtUvsyeEGoi .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-qbs2TgtUvsyeEGoi .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-qbs2TgtUvsyeEGoi .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-qbs2TgtUvsyeEGoi #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-qbs2TgtUvsyeEGoi .sequenceNumber{fill:white;}#mermaid-svg-qbs2TgtUvsyeEGoi #sequencenumber{fill:#333;}#mermaid-svg-qbs2TgtUvsyeEGoi #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-qbs2TgtUvsyeEGoi .messageText{fill:#333;stroke:none;}#mermaid-svg-qbs2TgtUvsyeEGoi .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-qbs2TgtUvsyeEGoi .labelText,#mermaid-svg-qbs2TgtUvsyeEGoi .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-qbs2TgtUvsyeEGoi .loopText,#mermaid-svg-qbs2TgtUvsyeEGoi .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-qbs2TgtUvsyeEGoi .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-qbs2TgtUvsyeEGoi .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-qbs2TgtUvsyeEGoi .noteText,#mermaid-svg-qbs2TgtUvsyeEGoi .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-qbs2TgtUvsyeEGoi .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qbs2TgtUvsyeEGoi .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qbs2TgtUvsyeEGoi .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qbs2TgtUvsyeEGoi .actorPopupMenu{position:absolute;}#mermaid-svg-qbs2TgtUvsyeEGoi .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-qbs2TgtUvsyeEGoi .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-qbs2TgtUvsyeEGoi .actor-man circle,#mermaid-svg-qbs2TgtUvsyeEGoi line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-qbs2TgtUvsyeEGoi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 内层事务全程使用 C2 外层继续用 C1,互不感知 doBegin 绑定 C1调用内层方法(REQUIRED_NEW)suspend:解绑 C1 装进 Holder 盒子doBegin 借新连接 C2 绑定 TLcommit(C2) 后解绑resume:开盒,把 C1 原样放回
两个容易被忽略的细节:suspend(null)(无事务时也挂"同步")传 null 时盒子是空盒,这是 getTransaction 新建分支里也先 suspend 的原因------纯粹为了一致性;REQUIRES_NEW 开新事务失败时 resumeAfterBeginException 会先恢复外层再抛异常,保证外层不无故陪葬。
⚠️ 踩坑二:rollback-only 转 UnexpectedRollbackException 的精确坐标
上一篇只说了"外层 commit 会转回滚并抛异常",这次把坐标钉死了------就在 commit() 的第 767 行:
java
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
processRollback(defStatus, true); // unexpected=true → 最后抛 UnexpectedRollbackException
return;
}
AbstractPlatformTransactionManager.java#commit------全局 rollback-only 在进入真正 commit 之前就被拦截。
而且标记动作本身也找到了:processRollback 里 REQUIRED 加入外层的内层事务失败时(902-927 行),走的不是 doRollback 而是 doSetRollbackOnly------DataSourceTransactionManager 的实现只是把 ConnectionHolder 标记为 rollback-only。整个闭环是:
内层 doSetRollbackOnly 标记 → 外层 commit() 767 行命中检查 → processRollback(defStatus, true) → 末尾 951 行抛 UnexpectedRollbackException。
回滚权属于事务的开启者,这句话的代码形态就是:内层只碰标记,doRollback 只有 isNewTransaction() 才调得动。教训一句话:在事务边界内 catch 内层异常想强行提交,撞的不是"最佳实践",是这段写死的检查。
第三步:一个注解注册三个 Bean,代理由"基础设施"自动织
最后一块拼图是注册链路。@EnableTransactionManagement 上标着 @Import(TransactionManagementConfigurationSelector.class),后者在默认的 PROXY 模式下往容器里注册两样东西:AutoProxyRegistrar(触发 AopConfigUtils.registerAutoProxyCreatorIfNecessary,注册 AOP 自动代理创建器)和 ProxyTransactionManagementConfiguration(一个配置类)。后者就是三个 @Bean:
java
@Bean // ① advisor = 切点 + 通知的绑定体
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
advisor.setTransactionAttributeSource(transactionAttributeSource()); // 切点:谁有 @Transactional
advisor.setAdvice(transactionInterceptor()); // 通知:TransactionInterceptor
}
@Bean // ② 切点的"眼睛":解析 @Transactional 元信息
public TransactionAttributeSource transactionAttributeSource() { return new AnnotationTransactionAttributeSource(); }
@Bean // ③ 通知本身:上一篇讲的那两行 invoke 就在这里落地
public TransactionInterceptor transactionInterceptor() { ... }
ProxyTransactionManagementConfiguration.java 节选精简------三个 Bean 的角色注释。
代理的生成时机不在注册时,而在每个 Bean 的后处理阶段:自动代理创建器(AbstractAutoProxyCreator)看到容器里有一个 advisor,就问它的切点"这个 Bean 的方法上有 @Transactional 吗"------AnnotationTransactionAttributeSource 现场解析注解元信息,命中则给 Bean 生成代理、把 advisor 织入拦截器链。@EnableTransactionManagement 自己不碰任何业务 Bean,它只是把"切点+通知"放上架,剩下的是 Spring AOP 通用机制自动完成------这也解释了为什么事务拦截天然和 @Async、自定义切面共用同一条拦截器链,顺序由 advisor 的 order 决定。
至此两篇拼成完整链路:注册期 (注解 → 三个 Bean → AOP 织代理)→ 运行期(invoke → getTransaction 两层瀑布分派 → doBegin/savepoint/suspend → 业务 → commit/rollback 按标记收尾)。
R --- Result:11 条路径全部回溯,两个黑盒拆穿
| 结论 | 源码出处 | 状态 |
|---|---|---|
| 传播行为分派 = getTransaction 一刀(有无事务)+ 两个 if-else 瀑布 | getTransaction 357-361 / handleExistingTransaction 428-503 |
✅ |
| 挂起的物理动作 = ThreadLocal 解绑连接 + 摘五个线程状态,装进 Holder | suspend 631-666 + doSuspend 344-352 |
✅ |
| 恢复 = 开盒原样绑回,挂在外层事务对象上流转 | resume 678-695 + doResume 355-357 |
✅ |
NESTED 与 REQUIRES_NEW 分家点:后者 suspend+doBegin 换连接,前者仅 setSavepoint |
handleExistingTransaction 447-503 |
✅ |
NESTED 正常结束只释放保存点,不做数据库 commit |
processCommit 796-801 releaseHeldSavepoint |
✅ |
rollback-only 检查在 commit 入口,转抛 UnexpectedRollbackException |
commit 767-773 + processRollback 951-953 |
✅ |
| 注册链路 = Selector → AutoProxyRegistrar + 三个基础设施 Bean,代理由 AOP 通用机制织 | TransactionManagementConfigurationSelector 47-57 / ProxyTransactionManagementConfiguration 48-80 |
✅ |
本篇沉淀与上篇合入同一个总结文档体系(事务执行流程.md 及本篇配图),传播行为部分可直接对照行号复习。
沉淀的三条原则
- 被"表格化"的知识,去源码里找它的一维形态------七种传播行为背成二维表格,源码里其实是两层 if 的线性判断;找回一维形态后,"哪种行为走哪条路"从记忆题变成读代码题;
- 黑盒名词先问物理动作------"挂起"听着像调度器,物理上就是"从 ThreadLocal 摘连接装盒子";把术语翻译成数据结构操作,理解和转述都会准确一个量级;
- 闭环要闭环到行号:上一篇"commit 转回滚抛异常"只到方法级,这一篇钉到 767 行检查、951 行抛出------排查线上这类异常时,行号级理解直接决定你能不能第一时间想到是谁标的记。
遗留事项
注册链路只读到了 advisor 上架为止,AbstractAutoProxyCreator 内部"如何找到 advisor、如何生成 CGLIB/JDK 代理、拦截器链怎么排序"属于 Spring AOP 通用机制,值得单开一篇;TransactionSynchronization(事务同步回调)的触发时机(triggerBeforeCommit 等钩子)也没有展开,它与"事务提交后再刷缓存/发消息"的实战模式直接相关,一并留作下一篇的候选主题。
本文全部基于开源框架(Spring)的公开源码研读,代码片段为源码节选精简,无敏感信息。