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)都支持,但太老旧的可能不支持。
相关推荐
zlwool5 小时前
图纸管理选型避坑指南
数据库·erp·设备erp·非标机械
EXI-小洲6 小时前
Java 操作 Word:字符串替换、图片插入、动态生成表格与API接口下载
java·开发语言·spring boot·word
小灰灰搞电子7 小时前
完全驾驭 Qt 与数据库:C++ ORM 框架 QxOrm 原理与实践指南
数据库·c++·qt
2601_961901707 小时前
SpringBoot使用Nacos进行application.yml配置管理
java·spring boot·spring
何以解忧,唯有..8 小时前
LangChain 工具调用(Tool Calling)实战指南
java·前端·langchain
QQ_21696290968 小时前
【源码编号:project79475】SpringBoot校内二手交易平台:商品发布、分类检索、留言交流、订单管理全流程实战
java·spring boot·后端
软件聚导航8 小时前
「聚小软 AI 助手」技术升级:从数据库检索到 RAG 知识库问答
前端·数据库·mysql·微信小程序·小程序·ai编程·rag
学习星球9 小时前
单调栈——从“找下一个更大的“到柱状图中的最大矩形
数据库·c++·算法·leetcode·xcode
前端 贾公子9 小时前
第09章:上下文与记忆 (2)
java·服务器·前端
2601_962203519 小时前
Java进阶07集合(续)
java·开发语言