面试高频: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:保存点回滚机制;
    • 其余策略用于控制事务的存在性和参与方式。

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

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

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

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

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

相关推荐
愈努力俞幸运6 分钟前
rust安装
开发语言·后端·rust
踏浪无痕10 分钟前
JobFlow 负载感知调度:把任务分给最闲的机器
后端·架构·开源
UrbanJazzerati12 分钟前
Python自动化统计工具实战:Python批量分析Salesforce DML操作与错误处理
后端·面试
Van_Moonlight16 分钟前
RN for OpenHarmony 实战 TodoList 项目:任务完成进度条
javascript·开源·harmonyos
我爱娃哈哈22 分钟前
SpringBoot + Seata + Nacos:分布式事务落地实战,订单-库存一致性全解析
spring boot·分布式·后端
Van_Moonlight24 分钟前
RN for OpenHarmony 实战 TodoList 项目:深色浅色主题切换
javascript·开源·harmonyos
小贵子的博客26 分钟前
Ant Design Vue <a-table>
前端·javascript·vue.js·anti-design-vue
天天进步201529 分钟前
【Nanobrowser源码分析4】交互篇: 从指令到动作:模拟点击、滚动与输入的底层实现
开发语言·javascript·ecmascript
nil32 分钟前
记录protoc生成代码将optional改成omitepty问题
后端·go·protobuf
console.log('npc')35 分钟前
vue2中子组件父组件的修改参数
开发语言·前端·javascript