面试高频:Spring 事务传播行为的核心价值是什么?

原文来自于:zha-ge.cn/java/121

面试高频:Spring 事务传播行为的核心价值是什么?

面试的时候,有一道老八股我几乎每次都能碰上:

"你能说说 Spring 的事务传播行为有几种吗?各自的作用是什么?"

刚入行的时候我也背得滚瓜烂熟:REQUIREDREQUIRES_NEWNESTED......七种!

但有一次项目上线翻车,让我彻底明白了一个道理:事务传播从来不只是"几种模式的名字",它是保证业务一致性的"行为哲学"。


那次"事务传染病"事故

故事要从一个"插日志"的小需求说起:

主业务下订单,订单成功后插一条日志,日志失败也不能影响主业务。

我心想:这不简单?加个 @Transactional 就完事:

typescript 复制代码
@Service
public class LogService {
    @Transactional
    public void saveLog(String msg) {
        if (msg.contains("fail")) throw new RuntimeException("日志写入失败");
    }
}

然后主流程这样调用:

typescript 复制代码
@Service
public class OrderService {
    @Transactional
    public void createOrder() {
        // 1. 创建订单
        orderRepository.save(...);
        // 2. 写日志
        logService.saveLog("fail");
    }
}

结果------日志报错,订单也回滚了!主流程和子流程"同生共死"了。

我这才真正意识到:事务传播行为,决定的是不同事务方法之间"生死是否绑在一起"。


什么是事务传播行为?

一句话解释:传播行为(Propagation)决定了一个方法在被调用时,事务该怎么处理:是加入现有事务?新开一个?还是干脆不参加?

Spring 提供了 7 种常见的传播行为,默认是 REQUIRED

ini 复制代码
@Transactional(propagation = Propagation.REQUIRED)

如果你不写,默认就是它。


七大传播行为全解析


1. REQUIRED(默认)------有就用,没有就建

📌 行为:如果存在事务,就加入;如果不存在,就新建一个。

这也是最常见的情况,也是我上面"插日志"翻车的原因:子事务加入了主事务,失败就一起回滚。

适用场景:

大部分正常业务逻辑,不需要事务独立。


2. REQUIRES_NEW ------我就是要自己干!

📌 行为:不管外面有没有事务,我都新建一个事务,暂停外层事务。

这就是解决"插日志"问题的关键:

typescript 复制代码
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String msg) { ... }

这下日志方法再报错,也不会影响外层事务。外层事务会被挂起,日志的事务单独提交/回滚。

适用场景:

  • 日志、消息发送等不该影响主流程的操作
  • 审计、风控等独立性强的子操作

⚠️ 注意:必须跨 Service 调用才能生效(自调用不经过代理,传播策略不起作用)。


3. NESTED ------子事务有"后悔药"

📌 行为:如果有事务,就在当前事务中创建一个"保存点";没有事务就新建一个。

NESTED 看起来和 REQUIRES_NEW 有点像,但不同的是,它不暂停外层事务,而是在内部开了个"子事务",可以单独回滚到保存点。

举个例子:

  • 外层事务插入订单
  • 内层事务插入优惠券记录失败
  • 外层事务可以选择"只回滚子事务",订单不受影响

适用场景:

  • 主事务和子事务逻辑相关,但希望子事务可"后悔"
  • 常用于分阶段处理

⚠️ 前提:底层数据库和数据源必须支持 Savepoint。


4. SUPPORTS ------有就参与,没有就算了

📌 行为:有事务就加入,没有就以非事务方式运行。

适用场景:

  • 逻辑对事务无强依赖,但希望尽量复用事务
  • 多用于"可有可无"的读操作

5. MANDATORY ------必须要有事务,否则报错

📌 行为:当前必须有事务,否则直接抛异常。

适用场景:

  • 某些核心逻辑必须在事务中运行
  • 强制保证调用方已经开启事务

6. NOT_SUPPORTED ------我不想参加

📌 行为:如果存在事务,先挂起它,再以非事务方式执行。

适用场景:

  • 查询逻辑或者第三方调用不希望被事务包裹
  • 防止事务超时、锁竞争

7. NEVER ------我就不在事务里!

📌 行为:如果当前存在事务,直接抛异常。

适用场景:

  • 明确要求非事务环境,比如某些外部 API 或异步任务

踩坑瞬间:自调用 = 传播失效

记得我刚学 REQUIRES_NEW 的时候,最常犯的一个错就是"自己调自己":

typescript 复制代码
@Transactional
public void createOrder() {
    saveLog(); // 调用本类内部方法
}

结果事务传播根本没生效,子事务照样和主事务绑一起。

为什么?因为 Spring 的事务是通过代理对象实现的,自调用不会经过代理,传播行为就形同虚设。

👉 正确写法:

typescript 复制代码
@Autowired
private LogService logService;

@Transactional
public void createOrder() {
    logService.saveLog(); // 必须通过代理对象调用
}

面试官杀手锏回答

问:事务传播行为的核心价值是什么?

建议这样回答👇:

  • 传播行为的核心价值在于控制"事务边界" :决定当前方法在被事务方法调用时,是复用事务、创建新事务,还是不参与事务,从而实现复杂场景下的数据一致性和隔离性。

  • 通过灵活选择传播策略,我们可以解决主子事务独立分阶段回滚防止事务传染等实际问题。

  • 常用的有:

    • REQUIRED:默认行为,加入或新建事务;
    • REQUIRES_NEW:新事务独立提交回滚;
    • NESTED:保存点回滚机制;
    • 其余策略用于控制事务的存在性和参与方式。

写在最后:传播,是"事务的语言"

很多人把事务传播当成"配置选项",但在我看来,它更像是事务的"语言":

它决定了事务之间是共进退 ,还是各自为战 ;是强绑定 ,还是弱关联

如果说隔离级别解决的是"一个事务内部的数据安全",那传播行为解决的就是"多个事务之间的协作逻辑"。

一句话记住:传播行为的核心价值,是让事务不再只有"有或没有"的二元世界,而是能在复杂业务场景下,优雅地控制"我和你"的关系。

相关推荐
调试人生的显微镜3 小时前
iOS 代上架实战指南,从账号管理到使用 开心上架 上传IPA的完整流程
后端
本就一无所有 何惧重新开始3 小时前
Redis技术应用
java·数据库·spring boot·redis·后端·缓存
九十一4 小时前
websocket的连接原理
前端·javascript
低音钢琴4 小时前
【SpringBoot从初学者到专家的成长11】Spring Boot中的application.properties与application.yml详解
java·spring boot·后端
iuuia4 小时前
05--JavaScript基础语法(1)
开发语言·javascript·ecmascript
越千年4 小时前
用Go实现类似WinGet风格彩色进度条
后端
呼叫69454 小时前
为什么`for...of` 循环无法输出对象的自定义属性?
javascript
淳朴小学生4 小时前
静态代理和动态代理
后端
码流之上4 小时前
【一看就会一写就废 指间算法】执行操作后的最大 MEX —— 同余、哈希表
算法·面试