<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Transactional on 吾爱主机</title><link>https://blog.waihost.com/tags/transactional/</link><description>Recent content in Transactional on 吾爱主机</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Fri, 17 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://blog.waihost.com/tags/transactional/index.xml" rel="self" type="application/rss+xml"/><item><title>Spring 事务传播机制：REQUIRED、REQUIRES_NEW 与嵌套事务工程实践</title><link>https://blog.waihost.com/posts/spring-transaction-propagation-practice/</link><pubDate>Fri, 17 Jul 2026 00:00:00 +0800</pubDate><guid>https://blog.waihost.com/posts/spring-transaction-propagation-practice/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/spring-transaction-propagation-practice.svg" alt="Featured image of post Spring 事务传播机制：REQUIRED、REQUIRES_NEW 与嵌套事务工程实践" /&gt;&lt;p&gt;业务代码里最容易“看起来对、跑起来却不对”的一类问题，就是 &lt;strong&gt;事务边界&lt;/strong&gt;。方法上贴了 &lt;code&gt;@Transactional&lt;/code&gt;，并不等于“想回滚就一定回滚、想独立提交就一定独立提交”。真正决定行为的，是 &lt;strong&gt;传播行为（propagation）&lt;/strong&gt;、&lt;strong&gt;物理事务 vs 逻辑事务&lt;/strong&gt;、以及 Spring 默认的 &lt;strong&gt;代理拦截模型&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;本文基于 Spring Framework 官方事务文档，把 &lt;code&gt;REQUIRED&lt;/code&gt; / &lt;code&gt;REQUIRES_NEW&lt;/code&gt; / &lt;code&gt;NESTED&lt;/code&gt; 等传播语义、回滚规则与常见工程踩坑讲清楚，并给出可落地的写法与排查清单。&lt;/p&gt;
&lt;h2 id="一先分清物理事务-vs-逻辑事务"&gt;&lt;a href="#%e4%b8%80%e5%85%88%e5%88%86%e6%b8%85%e7%89%a9%e7%90%86%e4%ba%8b%e5%8a%a1-vs-%e9%80%bb%e8%be%91%e4%ba%8b%e5%8a%a1" class="header-anchor"&gt;&lt;/a&gt;一、先分清：物理事务 vs 逻辑事务
&lt;/h2&gt;&lt;p&gt;Spring 管理事务时，不能只看“有没有 &lt;code&gt;@Transactional&lt;/code&gt;”，而要区分两层：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;概念&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
					&lt;th&gt;工程直觉&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;物理事务（physical）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;底层资源上真正开启的事务（如一条 JDBC Connection 上的 &lt;code&gt;begin/commit/rollback&lt;/code&gt;）&lt;/td&gt;
					&lt;td&gt;能不能真正提交/回滚、锁何时释放&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;逻辑事务（logical）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;每个被 Spring 事务拦截的方法作用域&lt;/td&gt;
					&lt;td&gt;该方法能否单独标记 &lt;code&gt;rollback-only&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;传播行为决定的是：&lt;strong&gt;外层方法与内层方法之间，逻辑作用域如何映射到物理事务&lt;/strong&gt;。&lt;br&gt;
这正是很多“内层抛异常后外层还以为自己 commit 成功了”的根源。&lt;/p&gt;
&lt;h2 id="二七种传播行为速查"&gt;&lt;a href="#%e4%ba%8c%e4%b8%83%e7%a7%8d%e4%bc%a0%e6%92%ad%e8%a1%8c%e4%b8%ba%e9%80%9f%e6%9f%a5" class="header-anchor"&gt;&lt;/a&gt;二、七种传播行为速查
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;@Transactional(propagation = …)&lt;/code&gt; 对应 &lt;code&gt;TransactionDefinition&lt;/code&gt; 中的常量。按“有没有外层事务”理解最稳：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;传播行为&lt;/th&gt;
					&lt;th&gt;无外层事务时&lt;/th&gt;
					&lt;th&gt;有外层事务时&lt;/th&gt;
					&lt;th&gt;典型用途&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;REQUIRED&lt;/code&gt;（默认）&lt;/td&gt;
					&lt;td&gt;新建物理事务&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;加入&lt;/strong&gt;外层同一物理事务&lt;/td&gt;
					&lt;td&gt;服务门面 + 多仓储同事务&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;SUPPORTS&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;非事务执行&lt;/td&gt;
					&lt;td&gt;加入外层事务&lt;/td&gt;
					&lt;td&gt;查询既可独立也可参与写事务&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;MANDATORY&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;抛异常&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;加入外层事务&lt;/td&gt;
					&lt;td&gt;强制调用方必须已开事务&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;REQUIRES_NEW&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;新建物理事务&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;挂起外层&lt;/strong&gt;，新建独立物理事务&lt;/td&gt;
					&lt;td&gt;审计日志、独立提交的状态点&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;NOT_SUPPORTED&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;非事务执行&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;挂起外层&lt;/strong&gt;，非事务执行&lt;/td&gt;
					&lt;td&gt;长耗时/远程调用不想占连接&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;NEVER&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;非事务执行&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;抛异常&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;明确禁止在事务中调用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;NESTED&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;等同 &lt;code&gt;REQUIRED&lt;/code&gt; 新建&lt;/td&gt;
					&lt;td&gt;同一物理事务内建 &lt;strong&gt;savepoint&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;内层可局部回滚，外层可继续&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;日常工程里真正高频的是：&lt;strong&gt;&lt;code&gt;REQUIRED&lt;/code&gt;、&lt;code&gt;REQUIRES_NEW&lt;/code&gt;、&lt;code&gt;NESTED&lt;/code&gt;&lt;/strong&gt;。下面展开它们的语义差异。&lt;/p&gt;
&lt;h2 id="三required默认正确但要懂-unexpectedrollbackexception"&gt;&lt;a href="#%e4%b8%89required%e9%bb%98%e8%ae%a4%e6%ad%a3%e7%a1%ae%e4%bd%86%e8%a6%81%e6%87%82-unexpectedrollbackexception" class="header-anchor"&gt;&lt;/a&gt;三、REQUIRED：默认正确，但要懂 UnexpectedRollbackException
&lt;/h2&gt;&lt;h3 id="1-语义"&gt;&lt;a href="#1-%e8%af%ad%e4%b9%89" class="header-anchor"&gt;&lt;/a&gt;1. 语义
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;PROPAGATION_REQUIRED&lt;/code&gt; 会 &lt;strong&gt;强制存在物理事务&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前没有事务 → 为本方法新建；&lt;/li&gt;
&lt;li&gt;已有外层事务 → &lt;strong&gt;参与&lt;/strong&gt;该外层物理事务（同一连接、同一提交点）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这很适合：一个 Service 编排多个 Repository，要求“要么一起成功，要么一起失败”。&lt;/p&gt;
&lt;h3 id="2-逻辑作用域仍是独立的"&gt;&lt;a href="#2-%e9%80%bb%e8%be%91%e4%bd%9c%e7%94%a8%e5%9f%9f%e4%bb%8d%e6%98%af%e7%8b%ac%e7%ab%8b%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;2. 逻辑作用域仍是独立的
&lt;/h3&gt;&lt;p&gt;即便映射到同一物理事务，Spring 仍会为每个 &lt;code&gt;REQUIRED&lt;/code&gt; 方法创建 &lt;strong&gt;逻辑事务作用域&lt;/strong&gt;。每个作用域可以独立设置 &lt;code&gt;rollback-only&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;关键后果（官方明确描述）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;内层逻辑事务标记了 rollback-only；&lt;/li&gt;
&lt;li&gt;外层自己没有决定回滚，仍尝试 commit；&lt;/li&gt;
&lt;li&gt;物理事务最终回滚，外层会收到 &lt;strong&gt;&lt;code&gt;UnexpectedRollbackException&lt;/code&gt;&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这不是“Spring 抽风”，而是保护调用方 &lt;strong&gt;绝不能误以为已经提交成功&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="3-内层-isolation--timeout--readonly-默认会被忽略"&gt;&lt;a href="#3-%e5%86%85%e5%b1%82-isolation--timeout--readonly-%e9%bb%98%e8%ae%a4%e4%bc%9a%e8%a2%ab%e5%bf%bd%e7%95%a5" class="header-anchor"&gt;&lt;/a&gt;3. 内层 isolation / timeout / readOnly 默认会被忽略
&lt;/h3&gt;&lt;p&gt;参与外层事务时，内层声明的隔离级别、超时、只读标志 &lt;strong&gt;默认静默忽略&lt;/strong&gt;，以外层特征为准。&lt;br&gt;
若希望“内层要求与外层不一致时直接失败”，可在事务管理器上打开 &lt;code&gt;validateExistingTransaction=true&lt;/code&gt;（非宽松模式也会拒绝只读/读写不匹配）。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 伪配置示意：开启后，内层 isolation/readOnly 与外层冲突会拒绝加入&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// PlatformTransactionManager 具体实现上设置 validateExistingTransaction = true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="四requires_new真正的独立事务"&gt;&lt;a href="#%e5%9b%9brequires_new%e7%9c%9f%e6%ad%a3%e7%9a%84%e7%8b%ac%e7%ab%8b%e4%ba%8b%e5%8a%a1" class="header-anchor"&gt;&lt;/a&gt;四、REQUIRES_NEW：真正的“独立事务”
&lt;/h2&gt;&lt;h3 id="1-语义-1"&gt;&lt;a href="#1-%e8%af%ad%e4%b9%89-1" class="header-anchor"&gt;&lt;/a&gt;1. 语义
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;PROPAGATION_REQUIRES_NEW&lt;/code&gt; &lt;strong&gt;总是&lt;/strong&gt;开启独立物理事务：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有外层事务 → &lt;strong&gt;挂起&lt;/strong&gt;外层，内层用新资源事务；&lt;/li&gt;
&lt;li&gt;内层 commit/rollback &lt;strong&gt;不影响&lt;/strong&gt;外层事务的提交结果；&lt;/li&gt;
&lt;li&gt;内层结束后，其持有的锁会尽快释放；&lt;/li&gt;
&lt;li&gt;内层可使用自己的 isolation / timeout / readOnly。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-适合什么"&gt;&lt;a href="#2-%e9%80%82%e5%90%88%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;2. 适合什么
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;操作日志 / 审计记录：业务主事务回滚，日志仍要留下；&lt;/li&gt;
&lt;li&gt;状态机“已到达某检查点”的独立落库；&lt;/li&gt;
&lt;li&gt;需要更短锁持有时间、且允许与外层最终结果不一致的写路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-连接池风险必须重视"&gt;&lt;a href="#3-%e8%bf%9e%e6%8e%a5%e6%b1%a0%e9%a3%8e%e9%99%a9%e5%bf%85%e9%a1%bb%e9%87%8d%e8%a7%86" class="header-anchor"&gt;&lt;/a&gt;3. 连接池风险（必须重视）
&lt;/h3&gt;&lt;p&gt;官方明确警告：外层事务仍绑定着资源时，内层还要再拿 &lt;strong&gt;新的数据库连接&lt;/strong&gt;。若连接池偏小，可能出现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;连接池耗尽；&lt;/li&gt;
&lt;li&gt;多线程都在外层事务中等待内层连接 → &lt;strong&gt;死锁式等待&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实践建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;连接池最大连接数要 &lt;strong&gt;大于并发线程数&lt;/strong&gt;（官方表述：至少比并发线程数多 1，实际应按峰值嵌套深度留余量）；&lt;/li&gt;
&lt;li&gt;不要在循环体里高频 &lt;code&gt;REQUIRES_NEW&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;先问自己：真的需要“独立提交”，还是只是想“内层失败不要拖死外层”——后者可能 &lt;code&gt;NESTED&lt;/code&gt; 更合适。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AuditService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auditService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OrderRepository&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orderRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 默认 REQUIRED&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderCmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orderRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 审计希望独立提交：业务失败也不丢“尝试下单”痕迹&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auditService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;recordAttempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;PLACE_ORDER&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ... 后续业务可能抛异常导致外层回滚&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orderRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;markPaid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuditService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propagation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Propagation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;REQUIRES_NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;recordAttempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 独立物理事务：这里的提交不会被外层回滚“带走”&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="五nested同一物理事务上的-savepoint"&gt;&lt;a href="#%e4%ba%94nested%e5%90%8c%e4%b8%80%e7%89%a9%e7%90%86%e4%ba%8b%e5%8a%a1%e4%b8%8a%e7%9a%84-savepoint" class="header-anchor"&gt;&lt;/a&gt;五、NESTED：同一物理事务上的 savepoint
&lt;/h2&gt;&lt;h3 id="1-语义-2"&gt;&lt;a href="#1-%e8%af%ad%e4%b9%89-2" class="header-anchor"&gt;&lt;/a&gt;1. 语义
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;PROPAGATION_NESTED&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;一个物理事务&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;内层通过 &lt;strong&gt;savepoint（保存点）&lt;/strong&gt; 支持局部回滚；&lt;/li&gt;
&lt;li&gt;内层回滚到 savepoint 后，&lt;strong&gt;外层仍可继续&lt;/strong&gt;并最终 commit/rollback。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它通常映射到 &lt;strong&gt;JDBC savepoint&lt;/strong&gt;，因此：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;依赖 JDBC 资源事务；&lt;/li&gt;
&lt;li&gt;与 &lt;code&gt;DataSourceTransactionManager&lt;/code&gt; 一类管理器配合最常见；&lt;/li&gt;
&lt;li&gt;不是所有事务管理器/资源都支持嵌套语义。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-与-requires_new-的选型"&gt;&lt;a href="#2-%e4%b8%8e-requires_new-%e7%9a%84%e9%80%89%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;2. 与 REQUIRES_NEW 的选型
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;&lt;code&gt;REQUIRES_NEW&lt;/code&gt;&lt;/th&gt;
					&lt;th&gt;&lt;code&gt;NESTED&lt;/code&gt;&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;物理事务&lt;/td&gt;
					&lt;td&gt;独立两条&lt;/td&gt;
					&lt;td&gt;仍是一条&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;内层回滚&lt;/td&gt;
					&lt;td&gt;不影响外层提交决策&lt;/td&gt;
					&lt;td&gt;只回滚到 savepoint，外层可继续&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;内层提交可见性&lt;/td&gt;
					&lt;td&gt;内层结束即可对外可见（独立提交）&lt;/td&gt;
					&lt;td&gt;仍受外层最终 commit 约束&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;连接占用&lt;/td&gt;
					&lt;td&gt;可能额外占连接&lt;/td&gt;
					&lt;td&gt;一般不另开连接&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;资源支持&lt;/td&gt;
					&lt;td&gt;广泛&lt;/td&gt;
					&lt;td&gt;依赖 savepoint 支持&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;口诀：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;要“内层 &lt;strong&gt;真正提交&lt;/strong&gt;，外层怎么回滚都保留” → &lt;code&gt;REQUIRES_NEW&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;要“内层失败可局部撤销，但最终仍跟外层同生共死” → &lt;code&gt;NESTED&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="六回滚规则默认只认-runtimeexception--error"&gt;&lt;a href="#%e5%85%ad%e5%9b%9e%e6%bb%9a%e8%a7%84%e5%88%99%e9%bb%98%e8%ae%a4%e5%8f%aa%e8%ae%a4-runtimeexception--error" class="header-anchor"&gt;&lt;/a&gt;六、回滚规则：默认只认 RuntimeException / Error
&lt;/h2&gt;&lt;p&gt;声明式事务的默认策略（官方 &lt;code&gt;@Transactional&lt;/code&gt; 默认值）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;传播：&lt;code&gt;REQUIRED&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;隔离：&lt;code&gt;ISOLATION_DEFAULT&lt;/code&gt;（交给底层数据源默认）&lt;/li&gt;
&lt;li&gt;读写：可写&lt;/li&gt;
&lt;li&gt;超时：底层默认 / 无&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回滚触发&lt;/strong&gt;：&lt;code&gt;RuntimeException&lt;/code&gt; 与 &lt;code&gt;Error&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认不回滚&lt;/strong&gt;：受检异常（checked &lt;code&gt;Exception&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这解释了一个经典坑：业务方法 &lt;code&gt;throws BusinessException extends Exception&lt;/code&gt;，调用方 catch 后发现 &lt;strong&gt;数据居然提交了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;可显式配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rollbackFor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;throws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BusinessException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 受检业务异常也会触发回滚&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;noRollbackFor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InventoryNotEnoughException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;reserveStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;skuId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 某些“预期内”的运行时异常不希望回滚时使用（务必非常克制）&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;也可在 catch 后程序化标记：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NoProductInStockException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TransactionAspectSupport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentTransactionStatus&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setRollbackOnly&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;官方仍建议优先用声明式 &lt;code&gt;rollbackFor&lt;/code&gt; / &lt;code&gt;noRollbackFor&lt;/code&gt;，少用手写 &lt;code&gt;setRollbackOnly()&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="七代理模型自调用为什么注解失效"&gt;&lt;a href="#%e4%b8%83%e4%bb%a3%e7%90%86%e6%a8%a1%e5%9e%8b%e8%87%aa%e8%b0%83%e7%94%a8%e4%b8%ba%e4%bb%80%e4%b9%88%e6%b3%a8%e8%a7%a3%e5%a4%b1%e6%95%88" class="header-anchor"&gt;&lt;/a&gt;七、代理模型：自调用为什么“注解失效”
&lt;/h2&gt;&lt;p&gt;默认 &lt;strong&gt;proxy 模式&lt;/strong&gt;（非 AspectJ 编织）下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;只有 &lt;strong&gt;通过代理进来的外部调用&lt;/strong&gt; 才会被拦截；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同类自调用&lt;/strong&gt;（&lt;code&gt;this.otherTxMethod()&lt;/code&gt;）不会走代理 → 内层 &lt;code&gt;@Transactional&lt;/code&gt; &lt;strong&gt;不生效&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;初始化阶段（如 &lt;code&gt;@PostConstruct&lt;/code&gt;）也不应依赖事务代理；&lt;/li&gt;
&lt;li&gt;Spring Framework 6.0 起，基于类的代理默认可对 &lt;code&gt;protected&lt;/code&gt; / 包可见方法生效；但 &lt;strong&gt;基于接口的 JDK 代理&lt;/strong&gt; 仍要求方法在接口中且为 &lt;code&gt;public&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PayService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 错误：自调用，markPaid 上的 REQUIRES_NEW 不会生效&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;markPaid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propagation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Propagation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;REQUIRES_NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;markPaid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;常见修法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把需要独立传播的方法拆到 &lt;strong&gt;另一个 Spring Bean&lt;/strong&gt;，通过注入调用；&lt;/li&gt;
&lt;li&gt;注入自身代理（&lt;code&gt;@Lazy&lt;/code&gt; 自注入 / &lt;code&gt;AopContext.currentProxy()&lt;/code&gt;，后者需暴露代理）；&lt;/li&gt;
&lt;li&gt;切换到 AspectJ 模式（成本更高，团队要统一约定）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="八可复现的排查清单"&gt;&lt;a href="#%e5%85%ab%e5%8f%af%e5%a4%8d%e7%8e%b0%e7%9a%84%e6%8e%92%e6%9f%a5%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;八、可复现的排查清单
&lt;/h2&gt;&lt;p&gt;当“事务行为不符合预期”时，按下面顺序收敛：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;是不是自调用？&lt;/strong&gt; 打断点看调用栈是否经过 &lt;code&gt;TransactionInterceptor&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异常类型是否触发默认回滚？&lt;/strong&gt; checked vs runtime；是否被上层吞掉。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传播是否是你以为的那个？&lt;/strong&gt; &lt;code&gt;REQUIRED&lt;/code&gt; 加入外层 ≠ &lt;code&gt;REQUIRES_NEW&lt;/code&gt; 独立提交。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;是否出现 &lt;code&gt;UnexpectedRollbackException&lt;/code&gt;？&lt;/strong&gt; 说明内层已 &lt;code&gt;rollback-only&lt;/code&gt;，外层还在 commit。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;连接池是否在 &lt;code&gt;REQUIRES_NEW&lt;/code&gt; 下打满？&lt;/strong&gt; 看获取连接耗时与 active connections。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只读事务是否被误用于写？&lt;/strong&gt; 以及是否开启了 &lt;code&gt;validateExistingTransaction&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多数据源 / 非 &lt;code&gt;DataSourceTransactionManager&lt;/code&gt; 场景&lt;/strong&gt; 下 &lt;code&gt;NESTED&lt;/code&gt; 是否根本不被支持。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最小实验建议（单测或本地）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;场景 A：outer REQUIRED → inner REQUIRED 抛 RuntimeException
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 期望：整体回滚；外层若吞异常仍可能 UnexpectedRollbackException
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;场景 B：outer REQUIRED → inner REQUIRES_NEW 成功后 outer 回滚
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 期望：inner 已提交数据仍在；outer 变更回滚
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;场景 C：同类 this.inner() 且 inner 标 REQUIRES_NEW
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 期望（proxy 默认）：inner 注解不生效，与 outer 同一逻辑路径
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="九工程建议可直接落到-code-review"&gt;&lt;a href="#%e4%b9%9d%e5%b7%a5%e7%a8%8b%e5%bb%ba%e8%ae%ae%e5%8f%af%e7%9b%b4%e6%8e%a5%e8%90%bd%e5%88%b0-code-review" class="header-anchor"&gt;&lt;/a&gt;九、工程建议（可直接落到 Code Review）
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;默认用 &lt;code&gt;REQUIRED&lt;/code&gt;&lt;/strong&gt;，不要到处 &lt;code&gt;REQUIRES_NEW&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;REQUIRES_NEW&lt;/code&gt; 只用于“必须独立提交”的窄路径，并评估连接池。&lt;/li&gt;
&lt;li&gt;需要局部回滚且最终仍同事务提交 → 优先评估 &lt;code&gt;NESTED&lt;/code&gt;，并确认 JDBC savepoint 支持。&lt;/li&gt;
&lt;li&gt;业务异常若走 checked，统一 &lt;code&gt;rollbackFor = Exception.class&lt;/code&gt; 或统一业务异常基类为 runtime。&lt;/li&gt;
&lt;li&gt;事务边界放在 &lt;strong&gt;应用服务层&lt;/strong&gt;，Repository 保持细粒度；避免 Controller 直接开事务导致边界失控。&lt;/li&gt;
&lt;li&gt;长耗时远程调用不要包在大事务里；必要时 &lt;code&gt;NOT_SUPPORTED&lt;/code&gt; 或先缩短事务再调外部。&lt;/li&gt;
&lt;li&gt;Code Review 必问：&lt;strong&gt;谁提交？谁回滚？失败时用户看到的状态与 DB 是否一致？&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="十总结"&gt;&lt;a href="#%e5%8d%81%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;十、总结
&lt;/h2&gt;&lt;p&gt;Spring 事务传播不是注解装饰，而是 &lt;strong&gt;逻辑作用域如何映射到物理资源事务&lt;/strong&gt; 的规则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;REQUIRED&lt;/code&gt;：共享物理事务，内层 &lt;code&gt;rollback-only&lt;/code&gt; 会拖垮外层提交预期；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;REQUIRES_NEW&lt;/code&gt;：独立物理事务，能独立提交，但要付连接与一致性代价；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NESTED&lt;/code&gt;：同一物理事务上的 savepoint，适合“局部失败、整体仍可继续”的路径；&lt;/li&gt;
&lt;li&gt;回滚默认只盯 &lt;code&gt;RuntimeException&lt;/code&gt;/&lt;code&gt;Error&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;代理模式下 &lt;strong&gt;自调用不走事务拦截&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把这五件事吃透，线上大部分“事务没生效 / 该回没回 / 不该提交却提交”的问题都能快速定位。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;Spring Framework Reference — &lt;a class="link" href="https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/tx-propagation.html" target="_blank" rel="noopener"
 &gt;Transaction Propagation&lt;/a&gt;（&lt;code&gt;REQUIRED&lt;/code&gt; / &lt;code&gt;REQUIRES_NEW&lt;/code&gt; / &lt;code&gt;NESTED&lt;/code&gt; 语义与 &lt;code&gt;UnexpectedRollbackException&lt;/code&gt;、连接池警告）&lt;/li&gt;
&lt;li&gt;Spring Framework Reference — &lt;a class="link" href="https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/rolling-back.html" target="_blank" rel="noopener"
 &gt;Rolling Back a Declarative Transaction&lt;/a&gt;（默认 RuntimeException/Error 回滚、&lt;code&gt;rollbackFor&lt;/code&gt;/&lt;code&gt;setRollbackOnly&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;Spring Framework Reference — &lt;a class="link" href="https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html" target="_blank" rel="noopener"
 &gt;Using @Transactional&lt;/a&gt;（默认传播/隔离/回滚规则、proxy 自调用限制、可见性）&lt;/li&gt;
&lt;li&gt;Spring Framework Javadoc — &lt;a class="link" href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/TransactionDefinition.html" target="_blank" rel="noopener"
 &gt;TransactionDefinition&lt;/a&gt;（七种 &lt;code&gt;PROPAGATION_*&lt;/code&gt; 常量定义）&lt;/li&gt;
&lt;/ol&gt;</description></item></channel></rss>