一、事务传播的本质
事务传播的核心是:当多个事务方法相互调用时,如何管理事务的边界。
Spring 通过 @Transactional(propagation = Propagation.XXX)
注解来控制这一行为,背后依赖数据库的“事务管理器”(如 DataSourceTransactionManager
)。
二、7 种传播行为的详细解释(附代码)
1.REQUIRED(默认)
行为:
-
如果当前有事务,则加入当前事务
-
如果当前没有事务,则新建一个事务
代码示例:
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 操作数据库
methodB(); // 调用另一个事务方法
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// 如果 methodA 有事务,methodB 会加入它;否则自己新建事务
}
典型场景:商城下单操作,下单和库存操作要么都成功、要么都失败
2.REQUIRES_NEW
行为:
-
无论如何都会新建一个事务,如果当前有事务则会将原来事务进行挂起
代码示例:
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 主事务操作
try {
methodB(); // 调用 REQUIRES_NEW 方法
} catch (Exception e) {
// 即使 methodB 失败,methodA 可以继续
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 强制开启新事务,与 methodA 的事务无关
}
典型场景:日志记录,无论其他业务是否成功都需要进行日志记录
3.NESTED
行为:
-
如果当前有事务,则创建一个嵌套事务
-
如果当前没有事务,等同于REQUIRED
代码示例:
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 主事务操作
try {
methodB(); // 嵌套事务
} catch (Exception e) {
// 只回滚 methodB 的操作,methodA 继续
}
}
@Transactional(propagation = Propagation.NESTED)
public void methodB() {
// 嵌套事务(可以部分回滚)
}
典型场景:
主订单(主事务)和订单详情(嵌套事务),无论订单详情是否成功都不会影响主订单
4.SUPPORTS
行为:
-
如果当前有事务就加入
-
如果当前没有事务就以无事务运行
典型场景:
查询数据,有事务就加入,没有事务也无所谓
5.MANDATORY
行为:
-
必须在事务中运行,否则会报异常
典型场景:
资金结算相关场景,要求必须在事务中运行
6.NOT_SUPPORTED
行为:
-
以非事务方式运行,如果有事务则挂起处理
典型场景:
发送短信通知,无论业务是否成功都发送
7.NEVER
行为:
-
必须在非事务中运行,如果有事务则抛异常
典型场景:
数据校验方法,必须在非事务中运行
三、关键原理剖析
1. 事务的底层实现
-
Spring 事务本质是通过 AOP 代理 实现的,调用事务方法时,会通过代理对象控制事务的开启、提交或回滚。
-
事务传播的核心逻辑:
-
根据传播类型,决定是否复用已有事务、挂起事务或抛出异常。
-
例如
REQUIRES_NEW
会调用doBegin()
方法强制开启新事务,并挂起旧事务。
-
2. 嵌套事务(NESTED)的特殊性
-
嵌套事务依赖数据库的 保存点(Savepoint) 机制(如 MySQL 的 InnoDB 支持)。
-
嵌套事务的回滚只会回滚到保存点,而外层事务可以继续。
-
注意:如果数据库不支持保存点,
NESTED
会退化为REQUIRED
。
3. 事务挂起与恢复
-
当调用
REQUIRES_NEW
或NOT_SUPPORTED
时,Spring 会挂起当前事务,将事务信息暂存到线程局部变量中,待新事务执行完毕后恢复。
四、实际开发中的坑
1. 事务不生效的常见原因
-
内部调用:同一个类中的方法调用(如
this.methodB()
)不会触发 AOP 代理,导致事务失效。
解决方案:通过AopContext.currentProxy()
获取代理对象调用。 -
异常被捕获:如果事务方法中的异常被
try-catch
捕获且未重新抛出,事务不会回滚。
解决方案:在catch
中手动回滚或抛出异常。
2. REQUIRES_NEW 的性能问题
-
频繁开启新事务会增加数据库连接池的压力,需谨慎使用。
五、面试高频问题
问题1:REQUIRED 和 NESTED 有什么区别?
-
REQUIRED:内外方法共用同一个事务,任何一个方法回滚会导致整个事务回滚。
-
NESTED:外层方法事务回滚会导致嵌套事务回滚,但嵌套事务可以独立回滚(通过保存点机制)。
问题2:事务方法中调用另一个事务方法,为什么事务不生效?
-
可能是内部调用(未通过代理对象),或异常被捕获未抛出。
总结表格
传播类型 | 行为描述(简版) | 适用场景 | 是否独立事务? | 是否受外部事务影响? | 高频搜索关键词关联 |
---|---|---|---|---|---|
REQUIRED | 默认!有事务则加入,无事务则新建 | 订单+库存操作、普通业务逻辑 | ❌ | ✅(整体回滚) | Spring事务传播、默认事务 |
REQUIRES_NEW | 强制新建事务,挂起外部事务 | 日志记录、异步任务 | ✅ | ❌(完全独立) | 独立事务、事务隔离 |
NESTED | 嵌套事务(基于保存点),外部事务回滚会连带嵌套事务,嵌套事务可单独回滚 | 订单主表+详情表 | 部分✅ | ✅(主事务影响子) | 嵌套事务、保存点 |
SUPPORTS | 有事务则用,没有则以非事务运行 | 查询操作 | ❌ | ✅ | 事务与非事务切换 |
NOT_SUPPORTED | 强制非事务运行,挂起外部事务 | 发送短信、通知 | ❌(非事务) | ❌ | 非事务执行 |
MANDATORY | 必须在事务中运行,否则抛异常 | 资金结算、核心业务 | ❌ | ✅ | 强制事务、事务校验 |
NEVER | 必须无事务运行,否则抛异常 | 数据校验、非事务方法 | ❌(非事务) | ❌ | 禁止事务、非事务调用 |
掌握事务传播机制,能让你在分布式系统和高并发场景下游刃有余。建议在开发中多用 REQUIRED
和 REQUIRES_NEW
,谨慎使用其他类型。