Spring 事务传播行为

Spring 事务传播行为

一、什么是事务传播行为

事务传播行为定义了一个事务方法被另一个事务方法调用时,事务如何传播,即被调用方法是在调用方的事务中执行,还是新开事务,还是不用事务。

Spring 共定义了 7 种传播行为 ,定义在 Propagation 枚举中。


二、七种传播行为详解

传播行为 说明 是否依赖已有事务 是否新建事务
REQUIRED 默认行为。有事务则加入,没有则新建 可新建
SUPPORTS 有事务就加入,没有就以非事务方式运行
MANDATORY 必须在已有事务中执行,没有则抛异常
REQUIRES_NEW 不管外部有无事务,总是新建一个独立事务,原事务挂起
NOT_SUPPORTED 以非事务方式运行,外部有事务则挂起
NEVER 必须以非事务方式运行,外部有事务则抛异常
NESTED 在已有事务中创建嵌套子事务(Savepoint 机制) ✅(子事务)

各传播行为详细说明
  1. REQUIRED(默认)

    • 最常用,覆盖 90% 场景
    • 有事务则融入,没有则创建新事务
    • 示例:订单创建调库存扣减,同事务同成功同失败
  2. SUPPORTS

    • 有事务跟着跑,没有就裸奔(非事务运行)
    • 适合可事务可不事务的只读操作
    • 示例:查询操作,有人调时有事务保一致性,没有也无所谓
  3. MANDATORY

    • 必须在已有事务中调用,否则抛 IllegalTransactionStateException
    • 适合调用方必须提供事务上下文的严格场景
  4. REQUIRES_NEW

    • 不管外部有没有事务,总是新开独立事务
    • 外部原事务被挂起,等新事务完成后恢复
    • 内外事务完全独立,互不影响
    • 示例:审计日志(主流程失败,日志也必须独立提交)
  5. NOT_SUPPORTED

    • 明确不要事务,有事务就挂起
    • 适合对一致性无要求的轻量操作
  6. NEVER

    • 绝对不能有事务,外部有事务直接抛异常
    • 适合严格要求非事务运行的场景
  7. NESTED

    • 在当前事务内开嵌套子事务,通过 JDBC Savepoint 实现
    • 子事务回滚只回滚到 Savepoint,不影响外层
    • 外层回滚会连带子事务一起回滚(与 REQUIRES_NEW 的关键区别)
    • 仅适用于 JDBC 单资源事务,JTA 分布式事务不支持
    • 示例:批量导入,每条一个子事务,某条失败只回滚那条

三、REQUIRES_NEW vs NESTED 核心对比

对比维度 REQUIRES_NEW NESTED
事务关系 完全独立的新事务 外层事务的子事务
外层回滚 不影响内层(已提交) 会连带回滚子事务
子事务回滚 不影响外层 不影响外层(回滚到 Savepoint)
实现机制 新建数据库物理连接/事务 Savepoint 机制
分布式事务支持 支持 不支持(仅 JDBC 单资源)

四、常用三种场景

1. REQUIRED(绝大多数场景)

订单创建 → 库存扣减,两个操作必须在同一事务中,要么一起成功,要么一起回滚。

java 复制代码
@Service
public class OrderService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder() {
        // 订单落库
        inventoryService.deduct(); // REQUIRED 自动加入当前事务
    }
}
2. REQUIRES_NEW(必须独立提交)

不管主流程成败,日志必须写入。

java 复制代码
@Service
public class AuditService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void recordLog() {
        // 转账即使失败,这条日志也必须写进去
    }
}
3. NESTED(部分失败可接受)

批量导入 1000 条数据,每条一个嵌套子事务,某条失败只回滚那条,其他继续。外层事务可设容错阈值,失败太多时整体回滚。

java 复制代码
@Service
public class ImportService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void batchImport(List<Data> list) {
        int failCount = 0;
        for (Data data : list) {
            try {
                importOne(data); // NESTED 子事务
            } catch (Exception e) {
                failCount++;
                if (failCount > threshold) {
                    throw new RuntimeException("失败过多,整体回滚");
                }
            }
        }
    }

    @Transactional(propagation = Propagation.NESTED)
    public void importOne(Data data) {
        // 单条导入逻辑
    }
}

五、总结

  • REQUIRED:默认,大锅饭模式,大多数场景够用
  • REQUIRES_NEW:独立出去单干,适合日志等必须落库的操作
  • NESTED:外层兜底,内部分批,适合批处理
  • 其他四种(SUPPORTS、MANDATORY、NOT_SUPPORTED、NEVER)用得较少,视具体需求选用