2、事务传播行为:Spring 事务传播机制详解

事务传播行为(Transaction Propagation)定义了当多个事务性方法相互调用时,事务边界如何合并或隔离。它是 Spring 声明式事务最核心的配置维度之一,直接影响开发环境中的事务边界调试结果。理解传播行为,是定位“事务未生效”“事务提前提交”“嵌套事务回滚异常”等问题的前提。

2.1 七种传播行为速览

Spring 通过 @Transactional(propagation = Propagation.XXX) 指定传播行为。下表列出全部七种模式及其核心语义:

传播行为 枚举值 核心语义 典型场景
REQUIRED Propagation.REQUIRED 支持当前事务;不存在则新建 默认行为,绝大多数业务方法
REQUIRES_NEW Propagation.REQUIRES_NEW 挂起当前事务,始终新建独立事务 操作日志、异步消息发送
NESTED Propagation.NESTED 基于保存点(Savepoint)的嵌套事务 批量子步骤回滚不影响主事务
SUPPORTS Propagation.SUPPORTS 支持当前事务;不存在则以非事务方式执行 只读查询,事务可选
NOT_SUPPORTED Propagation.NOT_SUPPORTED 挂起当前事务,以非事务方式执行 临时关闭事务的场景
MANDATORY Propagation.MANDATORY 必须存在当前事务,否则抛出异常 强制调用方已开启事务
NEVER Propagation.NEVER 必须不存在当前事务,否则抛出异常 禁止在事务内执行的操作
调试提示: 开发环境中最常遇到边界问题的三种传播行为是 REQUIRED、REQUIRES_NEW 和 NESTED。其余四种在可观测性调试中较少引发意外回滚或提交。

2.2 三种核心传播行为的事务边界对比

以下 SVG 图展示了 REQUIRED、REQUIRES_NEW、NESTED 在方法调用链中的事务边界差异。每个矩形代表一个事务边界,箭头表示方法调用方向。

三种核心传播行为的事务边界对比 场景 A: REQUIRED methodA() 事务 T1 methodB() 加入 T1 ← 合并到同一事务 场景 B: REQUIRES_NEW methodA() 事务 T1 methodB() 新事务 T2 ← T1 挂起,T2 独立 场景 C: NESTED methodA() 事务 T1 methodB() 保存点 S1 ← 嵌套在 T1 内 回滚行为对比 REQUIRED 回滚 methodB() 抛出异常 → T1 整体回滚 methodA() 的修改也丢失 REQUIRES_NEW 回滚 methodB() 抛出异常 → 仅 T2 回滚 T1 不受影响,可继续提交 NESTED 回滚 methodB() 抛出异常 → 回滚到保存点 S1 T1 可继续执行后续逻辑 传播行为对事务边界的影响总结 • REQUIRED:边界合并,调用链共享同一事务。任何内部异常都会导致整体回滚。 • REQUIRES_NEW:边界隔离,内部事务独立提交/回滚。外部事务不受内部异常影响。 • NESTED:边界嵌套,内部事务基于保存点。内部回滚可部分撤销,外部可继续。 • 调试关键:通过日志观察 TransactionSynchronizationManager 的激活事务数,可快速判断传播行为。

2.3 传播行为对事务边界的核心影响

传播行为直接决定了事务边界的三个关键属性:隔离性、回滚范围、提交时机。以下从可观测性调试角度逐一分析。

2.3.1 隔离性:事务是否独立

  • REQUIRED:无隔离。内部方法加入外部事务,共享同一个 JDBC 连接和事务上下文。
  • REQUIRES_NEW:完全隔离。内部方法获得全新的连接和事务 ID,外部事务被挂起。
  • NESTED:逻辑隔离。内部方法在外部事务内创建保存点,但共享同一连接。
可观测性要点: 在日志中启用 org.springframework.transaction.support.TransactionSynchronizationManager 的 DEBUG 级别,可以观察到 activate transaction [xxx] 和 deactivate transaction [xxx] 的切换。REQUIRES_NEW 会触发“挂起-恢复”日志序列,而 NESTED 则显示保存点创建与释放。

2.3.2 回滚范围:异常传播的边界

以下代码示例展示了三种传播行为在异常场景下的回滚差异:

@Service
public class OrderService {

    @Autowired
    private PaymentService paymentService;

    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        // 步骤1:插入订单(T1 的一部分)
        orderDao.insert(order);

        try {
            // 步骤2:调用支付(传播行为由 PaymentService 决定)
            paymentService.processPayment(order.getPayment());
        } catch (PaymentException e) {
            // 根据传播行为,这里的处理逻辑完全不同
            log.warn("支付失败,订单继续保留?", e);
        }

        // 步骤3:更新订单状态
        orderDao.updateStatus(order.getId(), "PENDING");
    }
}

@Service
public class PaymentService {

    // 场景1:REQUIRED —— 支付异常会导致整个 createOrder 回滚
    @Transactional(propagation = Propagation.REQUIRED)
    public void processPayment(Payment payment) {
        paymentDao.insert(payment);
        if (payment.getAmount() > 10000) {
            throw new PaymentException("大额支付需要人工审核");
        }
    }

    // 场景2:REQUIRES_NEW —— 支付异常只回滚支付本身,订单正常提交
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void processPayment(Payment payment) {
        // 同上
    }

    // 场景3:NESTED —— 支付异常回滚到保存点,订单可继续
    @Transactional(propagation = Propagation.NESTED)
    public void processPayment(Payment payment) {
        // 同上
    }
}
常见陷阱: 在 REQUIRED 模式下,即使外部方法用 try-catch 捕获了内部异常,事务仍然会被标记为 rollback-only。外部方法后续的任何数据库操作都会抛出 UnexpectedRollbackException。这是开发环境中最容易误判的事务边界问题。

2.3.3 提交时机:事务何时持久化

传播行为 提交时机 对可观测性的影响
REQUIRED 最外层方法结束时统一提交 内部方法的数据库变更在方法返回后不可见,直到外部方法完成
REQUIRES_NEW 内部方法返回时立即提交 内部方法的变更对外部方法立即可见(如果使用不同连接)
NESTED 外部方法提交时统一提交 内部方法的变更在外部提交前不可见,但保存点允许部分回滚

2.4 传播行为选择决策树

以下 SVG 图帮助你在实际开发中快速选择正确的传播行为:

传播行为选择决策树 内部方法需要事务吗? 不需要 → SUPPORTS 需要 → 继续判断 内部异常应影响外部? 是 → REQUIRED 整体回滚,原子性保证 需要独立提交吗? 是 → REQUIRES_NEW 独立提交/回滚,完全隔离 否 → NESTED 保存点回滚,外部可继续

2.5 开发环境调试清单

在开发环境中排查事务传播行为相关问题时,按以下清单逐项检查:

  1. 确认代理生效:@Transactional 方法必须通过 Spring AOP 代理调用(外部调用 → 代理对象 → 目标方法)。同类内部调用(this.method())会绕过代理,导致传播行为不生效。
  2. 检查事务管理器:确保 PlatformTransactionManager 已正确配置,且与数据源对应。多数据源场景下容易配错事务管理器。
  3. 观察事务同步管理器日志:启用 DEBUG 级别日志,观察 getTransaction、commit、rollback 的调用序列。
  4. 验证回滚规则:@Transactional(rollbackFor = Exception.class) 确保 checked exception 也能触发回滚。默认只回滚 RuntimeException 和 Error。
  5. 检查连接池活跃连接数:REQUIRES_NEW 会占用额外连接,连接池耗尽时可能导致死锁或超时。
调试利器: 在测试类中注入 TransactionSynchronizationManager,调用 getCurrentTransactionName() 和 isActualTransactionActive() 可以实时查看当前线程的事务状态。结合断点调试,能清晰看到事务边界的创建与销毁。