Spring 事务传播行为 + 事务失效原因 + 传播行为为什么不用其他模式

文章目录

7 种事务传播行为、默认行为的原因、内部调用为什么事务失效、为什么不用其他模式。


一、Spring 事务传播行为是什么?

Spring 的事务是基于 AOP(代理) 实现的,而传播行为(Propagation Behavior)定义了:

当一个带 @Transactional 的方法被调用时,它应该如何参与已有事务?

Spring 提供 7 种事务传播机制,每种机制都有不同的语义。


二、Spring 提供的 7 种事务传播行为

传播行为 说明 是否新建事务 是否加入现有事务 使用频率
REQUIRED(默认) 没有事务就创建,有事务就加入 ⭐⭐⭐⭐⭐
REQUIRES_NEW 开启新事务,挂起外部事务 ⭐⭐⭐
SUPPORTS 有事务就加入,没有就非事务执行 ⭐⭐
NESTED 外部事务中开启子事务(Savepoint) ✔(子事务) ⭐⭐
MANDATORY 必须在事务中,否则报错
NOT_SUPPORTED 不用事务,挂起当前事务
NEVER 禁止事务,否则报错

三、为什么 Spring 默认使用 REQUIRED?

默认传播行为是:

复制代码
PROPAGATION_REQUIRED

它的语义最符合业务场景:

  • 如果没有事务:创建一个事务
  • 如果已经有事务:加入旧事务

✔ 1. 最符合真实业务逻辑

下单例子:

复制代码
saveOrder()
saveOrderItem()
reduceStock()

它们必须:

要么全部成功,要么全部回滚

------ REQUIRED 刚好满足

✔ 2. 开销小、风险低

不会频繁挂起事务(相比 REQUIRES_NEW)。

不需要额外 savepoint(相比 NESTED)。

✔ 3. 覆盖 95% 的使用场景

因此成为默认。


四、为什么其他传播模式一般"很少用"?

📌 1. REQUIRES_NEW 为啥不常用?

它会:

  • 挂起外部事务
  • 开启一个新的事务

成本比较大:

  • 需要额外数据库连接
  • 可能造成数据不一致(外部回滚,但内部提交)

适合"写日志/写流水这种必须独立提交的业务"

不是常态。


📌 2. SUPPORTS 为啥不常用?

它的语义不稳定:

  • 有事务 → 加入
  • 无事务 → 无事务执行

对于开发来说,行为太不可控。

出现问题时更难排查。


📌 3. MANDATORYNEVER 基本不使用

因为:

  • 很少有人希望"必须有事务,否则报错"(MANDATORY)
  • 也很少有人希望"绝不能有事务,有就报错"(NEVER)

业务上没有这种强硬需求。


📌 4. NOT_SUPPORTED 为什么少?

它执行时会:

  • 挂起事务
  • 强制无事务执行

但你完全可以:

直接不加 @Transactional 就好了

没必要挂起事务,成本也大。


📌 5. NESTED 为什么很少用?

  • 要求数据库/JDBC 支持 Savepoint
  • 很多人误以为它 = REQUIRES_NEW(这是误区)
  • 使用场景更少

一般业务用不到。


五、为什么内部方法调用不会触发事务?

Spring 的事务通过 代理对象 实现:

复制代码
外部调用 → Proxy → 事务增强 → 真正方法

但是内部调用:

复制代码
A() 内部调用 B()
this.B()  // 不经过 Proxy!

Spring 完全检测不到这次调用,因此:

内部调用不会触发事务,也不会应用传播行为。

✔ 正确做法:

方案 1:将方法拆分到不同 Service

让 A 调 B 时经过代理:

java 复制代码
@Autowired
BService b;

public void A() {
    b.B(); // 通过代理对象调用
}

方案 2:强制获取自身代理调用

java 复制代码
AService proxy = (AService) AopContext.currentProxy();
proxy.B();

六、事务传播行为场景

如果 A 调用了 B,并且 A 有事务,那么 B 会加入到 A 的事务中,因为 REQUIRED 会在已有事务的上下文中执行方法。
如果 A 没有事务,B 的传播行为是 REQUIRED,Spring 会为 B 创建一个新的事务。

场景 1:无事务 → 调 A(REQUIRED)

复制代码
[无事务]
   ↓
调用 A()
   ↓
Spring:创建 Tx1

场景 2:A 有事务调用 B(REQUIRED)

复制代码
Tx1 已存在
   ↓
A() 调用 B()
   ↓
B 加入 Tx1

最终:

复制代码
一个事务:Tx1  
A → B → C

七、总结

✔ 内部方法调用不会触发事务(绕过代理)

✔ private 方法、不在同一个 Bean 中也不会触发事务

✔ 异常没抛出、不符合回滚规则 → 不会回滚

✔ Proxy 方式不对(比如 final 类)导致事务失效

Spring 默认使用 REQUIRED,因为:

"没有事务就创建,有事务就加入"

能保证整体业务的原子性

其他传播行为不常用是因为:

  • 场景小众(REQUIRES_NEW)
  • 行为不可控(SUPPORTS)
  • 依赖底层支持(NESTED)
  • 语义太强/太弱(MANDATORY、NEVER)
  • 不如不用注解(NOT_SUPPORTED)

内部调用不会触发事务是因为:

Spring 需要通过代理拦截方法,但内部 this.xxx() 调用绕过代理。

相关推荐
五阿哥永琪2 小时前
Spring中的定时任务怎么用?
java·后端·spring
倔强的石头_2 小时前
时序数据时代的“存储与分析困局”解析及金仓解决方案
数据库
计算机毕设VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue小型房屋租赁系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
TaiKuLaHa3 小时前
Spring Bean的生命周期
java·后端·spring
倔强的石头_3 小时前
场景化落地指南——金仓时序数据库在关键行业的应用实践
数据库
SelectDB3 小时前
驾驭 CPU 与编译器:Apache Doris 实现极致性能的底层逻辑
运维·数据库·apache
刀法如飞3 小时前
开箱即用的 DDD(领域驱动设计)工程脚手架,基于 Spring Boot 4.0.1 和 Java 21
java·spring boot·mysql·spring·设计模式·intellij-idea
zbguolei3 小时前
MySQL根据身份证号码计算出生日期和年龄
数据库·mysql