基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Spring 事务的传播行为(Propagation)有哪些?

知识点图片

在 Spring 框架中,事务的传播行为(Propagation Behavior)是指:当一个带有事务的方法被另一个方法调用时,Spring 应该如何处理这个事务?(例如:是加入现有事务,还是创建一个新事务,或者是抛出异常?)

Spring 在 Propagation 枚举类中定义了 7 种 事务传播行为。为了方便理解,我们可以将它们分为三大类:


第一类:支持当前事务(最常用)

这类传播行为的核心是:如果当前已经有事务了,就加入进去。

1. REQUIRED(Spring 默认)

  • 规则:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。
  • 特点:内外方法共享同一个物理事务。如果内部方法抛出异常发生回滚,外部方法也会跟着回滚。
  • 适用场景:绝大多数的业务场景(增删改),Spring 默认就是这个。

2. SUPPORTS

  • 规则:如果当前存在事务,则加入该事务;如果当前没有事务,就以非事务的方式继续运行。
  • 适用场景:通常用于查询操作。如果有其他事务带着它,它就跟着一起;如果没有,它自己也不强求。

3. MANDATORY

  • 规则:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
  • 适用场景:该方法必须在事务上下文中运行,通常用于某些绝对不能单独执行的核心底层方法。

第二类:不支持当前事务

这类传播行为的核心是:我不想和外部方法共用一个事务。

4. REQUIRES_NEW(非常常用,面试常考)

  • 规则无论当前是否存在事务,都会创建一个全新的事务。 如果当前存在事务,会将当前事务挂起(Suspend)
  • 特点:内部事务和外部事务是完全独立的。内部事务的提交或回滚,不会影响外部事务;外部事务的回滚,也不会影响内部事务。
  • 适用场景:记录操作日志。无论核心业务是成功还是失败(回滚),日志都必须被记录下来(提交)。

5. NOT_SUPPORTED

  • 规则:以非事务方式运行。如果当前存在事务,会将当前事务挂起
  • 适用场景:执行某些不需要事务的耗时操作(如发送邮件、调用外部远程 API),挂起事务可以避免长时间占用数据库连接和锁。

6. NEVER

  • 规则:以非事务方式运行。如果当前存在事务,则抛出异常
  • 适用场景:绝对不能在事务中运行的方法,起到严格校验的作用。

第三类:嵌套事务

7. NESTED

  • 规则:如果当前存在事务,则在嵌套事务(Nested Transaction)内执行;如果当前没有事务,则表现得和 REQUIRED 一样(创建一个新事务)。
  • 特点:依赖于 JDBC 的 Savepoint(保存点)机制。
    • 嵌套事务是外部事务的子事务
    • 如果内部(子)事务回滚,它只会回滚到自己执行前的保存点,不会导致外部事务回滚(前提是外部事务捕获了内部抛出的异常)。
    • 如果外部(父)事务回滚,则内部(子)事务一定会跟着回滚
    • 子事务只有在父事务提交时,才会一起提交。
  • 适用场景:复杂的批处理任务,其中某个子任务失败了允许跳过并记录,但不希望影响整个批处理任务的整体提交。

💡 核心对比与总结表

传播行为 如果有事务 如果没有事务
REQUIRED (默认) 加入当前事务 新建事务
SUPPORTS 加入当前事务 非事务执行
MANDATORY 加入当前事务 抛出异常
REQUIRES_NEW 挂起当前,新建事务 新建事务
NOT_SUPPORTED 挂起当前,非事务执行 非事务执行
NEVER 抛出异常 非事务执行
NESTED 创建保存点(嵌套执行) 新建事务

⚠️ 经典面试题 / 避坑指南

  1. REQUIRES_NEWNESTED 的区别是什么?
    • 独立性REQUIRES_NEW 是完全独立的两个事务,父事务回滚不影响子事务。NESTED 是父子关系,父事务回滚,子事务一定回滚。
    • 提交时机REQUIRES_NEW 的子事务执行完立即提交。NESTED 的子事务执行完不提交,必须等父事务结束才一起提交。
  2. 同类中方法调用,事务传播不生效的问题(AOP 失效)
    • 现象:在同一个类中,方法 A 没有 @Transactional,方法 B 有 @Transactional(propagation = Propagation.REQUIRES_NEW)。A 调用 B 时,B 的事务不会生效
    • 原因:Spring 事务是基于 AOP 动态代理实现的。同类内部的方法直接调用(this.B()),绕过了代理对象,直接调用了目标类的原始方法,因此事务切面不生效。
    • 解决办法
      1. 将方法 B 抽离到另一个 Service 类中被调用(推荐)。
      2. 在当前类中自己注入自己(Spring 循环依赖支持)。
      3. 使用 AopContext.currentProxy() 获取代理对象后再调用。
00:00
00:00