Spring 的事务传播行为(Propagation)

这确实是 Spring 事务中最烧脑、但也是设计最精妙的部分。

事务传播行为(Propagation Behavior) 解决的核心问题是:

当一个事务方法 A,调用了另一个事务方法 B 时,方法 B 应该怎么运行?

是 B 加入 A 的事务?还是 B 自己新开一个事务?还是 B 根本就不想要事务?

Spring 提供了 7 种 传播行为,但你只需要重点掌握 最常用的 3 种。为了让你彻底理解,我用**"老板派活"**来打比方。


场景设定

  • 方法 A (外部)ServiceA.methodA() ------ 比如"下订单"。

  • 方法 B (内部)ServiceB.methodB() ------ 比如"扣减库存" 或 "记录日志"。

  • 调用关系:A 调用 B。


1. REQUIRED (默认值,最常用)

口号:"有福同享,有难同当"

  • 含义:如果 A 已经有事务,B 就加入 A 的事务(合成一个大事务);如果 A 没有事务,B 就自己开一个新事务。

  • 比喻:老板(A)在做一个大项目,让你(B)来帮忙。如果你俩是一伙的,项目做砸了(异常),大家一起完蛋。

实战推演:

java 复制代码
// ServiceA
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    serviceB.methodB(); // 调用 B
    // ... A 自己的其他操作
}

// ServiceB
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    // ... B 的操作
}
  • 情况 1(B 报错) :B 抛出异常 -> A 和 B 都会回滚。因为他们是同一条船上的(同一个事务)。

  • 情况 2(A 报错) :A 在调用完 B 之后报错 -> A 和 B 都会回滚。就算 B 已经跑完了代码,但只要大事务提交失败,B 做的事也要吐出来。


2. REQUIRES_NEW (常用作"隔离")

口号:"各过各的,互不拖累"

  • 含义 :不管 A 有没有事务,B 永远开启一个新的独立事务 。如果 A 有事务,就把 A 的事务挂起(Suspend),等 B 的新事务跑完提交了,再恢复 A 的事务。

  • 比喻 :老板(A)在开会,突然想喝咖啡,让你(B)去买。你买咖啡这个动作(B)和老板开会(A)是两回事。就算老板开会开崩了(A 回滚),你的咖啡已经买好了(B 提交),不用退回去。

典型场景"记录日志"。无论业务逻辑(下订单)成功还是失败,我都要把日志记录进数据库,不能因为订单失败回滚,把我的报错日志也回滚没了。

实战推演:

java 复制代码
// ServiceA (下订单)
@Transactional(propagation = Propagation.REQUIRED)
public void checkout() {
    // 1. 尝试下单
    orderMapper.insertOrder();
    
    try {
        // 2. 记录日志 (不管下单成不成功,日志必须入库)
        logService.saveLog(); 
    } catch (Exception e) {
        // 这里的 try-catch 很重要!
        // 如果 B 报错,A 捕获后可以选择不回滚
    }
    
    // 3. 模拟异常
    int i = 1 / 0; 
}

// LogService (日志)
@Transactional(propagation = Propagation.REQUIRES_NEW) // <--- 重点
public void saveLog() {
    logMapper.insert();
}
  • 结果 :订单(A)回滚了,但是日志(B)保存成功了

  • 注意 :如果 B 报错了,A 必须 catch 住 B 的异常,否则 A 也会跟着回滚。


3. NESTED (嵌套事务,较少用但很高级)

口号:"留得青山在,不怕没柴烧"

  • 含义 :如果 A 有事务,B 就在 A 里面创建一个子事务(Savepoint/保存点)

  • 区别

    • REQUIRED 区别:B 报错回滚,只回滚 B 自己的部分 ,不会带着 A 一起死(前提是 A catch 住了 B)。

    • REQUIRES_NEW 区别:B 虽然是独立的逻辑,但B 还没提交 。如果 A 最后挂了,B 也要陪葬(回滚)。

  • 比喻:你在打游戏(A),打到 Boss 关前存了个档(B)。

    • 如果你打 Boss 输了(B 报错),你只需要读取存档(回滚 B),不用从游戏最开始重玩(A 不回滚)。

    • 但是,如果你的电脑突然断电了(A 报错/崩溃),那你的存档(B)也没了。


总结一张表 (面试/工作速查)

传播行为 A 有事务时 B 的行为 A 挂了,B 怎么办? B 挂了,A 怎么办? 典型应用场景
REQUIRED (默认) 加入 A B 回滚 A 回滚 绝大多数业务逻辑 (90%)
REQUIRES_NEW 挂起 A,B 开新事务 B 不回滚 (早已提交) A 可选择不回滚 (需 try-catch) 审计日志、发送流水
NESTED 在 A 中建保存点 B 回滚 (陪葬) A 可选择不回滚 (回滚到保存点) 复杂业务流中尝试性的步骤
SUPPORTS 加入 A B 回滚 A 回滚 只读查询
NOT_SUPPORTED 挂起 A,B 以非事务运行 / / 发送短信/邮件等耗时且非库操作

避坑指南

  1. REQUIRES_NEW 的死锁风险

    • 如果 A 更新了表 X 的行 1,然后调用 B。

    • B(新事务)也去更新表 X 的行 1。

    • 结果 :B 等着 A 释放锁,A 等着 B 执行完。死锁!

    • 建议 :使用 REQUIRES_NEW 时,尽量操作不同的表。

  2. NESTED 需要数据库支持

    • 它依赖 JDBC 的 Savepoint 机制。大部分主流数据库(MySQL, PostgreSQL)都支持,但太老旧的可能不支持。
相关推荐
一瓢西湖水5 小时前
列式数据库-以clickHouse为例
数据库·clickhouse
Elastic 中国社区官方博客5 小时前
使用 Elastic Cloud Serverless 扩展批量索引
大数据·运维·数据库·elasticsearch·搜索引擎·云原生·serverless
liulanba5 小时前
AI Agent技术完整指南 第一部分:基础理论
数据库·人工智能·oracle
十月南城5 小时前
Spring Cloud生态地图——注册、配置、网关、负载均衡与可观测的组合拳
spring·spring cloud·负载均衡
没有bug.的程序员5 小时前
服务安全:内部服务如何防止“裸奔”?
java·网络安全·云原生安全·服务安全·零信任架构·微服务安全·内部鉴权
逆天小北鼻6 小时前
Oracle 服务端与客户端的核心区分要点
数据库·oracle
2501_946242936 小时前
MPV-EASY Player (MPV播放器) v0.41.0.1
数据库·经验分享·云计算·计算机外设·github·电脑·csdn开发云
一线大码6 小时前
SpringBoot 3 和 4 的版本新特性和升级要点
java·spring boot·后端
weixin_440730506 小时前
java数组整理笔记
java·开发语言·笔记
weixin_425023006 小时前
Spring Boot 实用核心技巧汇总:日期格式化、线程管控、MCP服务、AOP进阶等
java·spring boot·后端