<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Spring on arjenzhou</title><link>/categories/spring/</link><description>Recent content in Spring on arjenzhou</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 22 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="/categories/spring/feed.xml" rel="self" type="application/rss+xml"/><item><title>当副作用遇上事务传播：一个时序问题的两次误判</title><link>/article/2026/09/transaction-side-effect-timing/</link><pubDate>Tue, 22 Sep 2026 00:00:00 +0000</pubDate><guid>/article/2026/09/transaction-side-effect-timing/</guid><description>&lt;p&gt;在基于 Spring 事务管理的系统中，业务副作用（如消息发送）与事务提交之间的时序一致性是一个容易被忽视的问题。当业务逻辑涉及嵌套方法调用，且内外层方法共享同一事务上下文时，&amp;ldquo;业务逻辑完成&amp;quot;与&amp;quot;事务提交&amp;quot;之间存在语义上的不等价性。&lt;/p&gt;
&lt;p&gt;本文通过两次生产故障的分析，揭示事务传播语义和事务同步回调生命周期中两个易被误判的时序节点。两次故障的共同特征是：出问题的代码本身逻辑正确，但被放入了预期之外的事务上下文。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注：本文中的代码均为伪代码，用于说明问题结构，不代表实际生产代码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id="问题背景"&gt;问题背景&lt;/h1&gt;
&lt;p&gt;系统中存在一个通用的状态机处理组件，负责状态流转并在流转完成后发送消息通知下游。该组件独立运行时工作正常——方法自身声明了 &lt;code&gt;@Transactional&lt;/code&gt;，状态变更提交后再发消息，时序无误。&lt;/p&gt;
&lt;p&gt;问题出现在该组件被其他业务代码调用时：外层调用方也持有事务，导致状态机组件不再是事务的发起者，其对提交时机的假设随之失效。&lt;/p&gt;
&lt;h1 id="第一次故障外层事务改变了提交边界"&gt;第一次故障：外层事务改变了提交边界&lt;/h1&gt;
&lt;h2 id="现象"&gt;现象&lt;/h2&gt;
&lt;p&gt;状态机组件在状态流转完成后直接发送消息，消费者反查数据库，发现数据不存在。&lt;/p&gt;
&lt;p&gt;状态机组件的代码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@Transactional&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;transition&lt;/span&gt;(Long orderId, State targetState) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stateMachineRepository.&lt;span style="color:#a6e22e"&gt;updateState&lt;/span&gt;(orderId, targetState);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; messageSender.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(orderId); &lt;span style="color:#75715e"&gt;// ← 状态流转完成，发送消息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@Transactional&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;createOrder&lt;/span&gt;(OrderRequest request) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; orderRepository.&lt;span style="color:#a6e22e"&gt;save&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Order(request));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stateMachineService.&lt;span style="color:#a6e22e"&gt;transition&lt;/span&gt;(request.&lt;span style="color:#a6e22e"&gt;getOrderId&lt;/span&gt;(), State.&lt;span style="color:#a6e22e"&gt;CREATED&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 后续逻辑...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&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;pre tabindex="0"&gt;&lt;code&gt;createOrder() ← 事务发起者（非状态机组件）
 └─ orderRepository.save()
 └─ transition() ← REQUIRED, 加入外层事务
 └─ stateMachineRepository.updateState() ← 写入状态变更
 └─ messageSender.send() ← 消息发出，但事务尚未提交
 └─ 后续逻辑...
事务提交 → Connection.commit() ← 真正的提交在这里
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="分析"&gt;分析&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;transition()&lt;/code&gt; 声明了 &lt;code&gt;@Transactional&lt;/code&gt;，传播行为为默认的 &lt;code&gt;REQUIRED&lt;/code&gt;。当 &lt;code&gt;transition()&lt;/code&gt; 独立调用时，它自身即为事务发起者，方法返回时触发提交，消息发送时序正确。但当 &lt;code&gt;createOrder()&lt;/code&gt; 在外层开启了事务时，&lt;code&gt;transition()&lt;/code&gt; 加入的是外层事务，两者映射到同一个物理事务，内层方法不具备独立的提交点。&lt;/p&gt;</description></item></channel></rss>