Spring Boot 本身不实现事务,底层用的是 Spring Framework 的事务管理。所谓“7 种事务传播机制”,就是 @Transactional(propagation = ...) 中的 Propagation 枚举:
@Transactional(propagation = Propagation.REQUIRED)
public void method() {}
7 种分别是:REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED。
核心判断逻辑是:当前线程是否已经存在事务?被调用方法希望如何对待这个事务?
1. REQUIRED:默认,有则加入,无则新建
@Transactional(propagation = Propagation.REQUIRED)
- 当前有事务:加入当前事务,成为同一个事务。
- 当前无事务:新建一个事务。
- 这是 Spring 默认传播行为。
特点:
- 内外层通常一起提交、一起回滚。
- 内层抛异常,即使外层
try-catch了,也可能因为事务被标记rollback-only,最终抛UnexpectedRollbackException。 - 适合绝大多数增删改业务。
场景:订单创建、用户注册、支付扣款等。
2. SUPPORTS:有则加入,无则以非事务执行
@Transactional(propagation = Propagation.SUPPORTS)
- 当前有事务:加入当前事务。
- 当前无事务:不新建事务,以非事务方式执行。
特点:
- 不强制事务。
- 适合查询方法。
- 如果被事务方法调用,它就在事务里;如果被非事务方法调用,它就不在事务里。
场景:查询、只读操作。
常配合:
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
3. MANDATORY:必须存在事务,否则报错
@Transactional(propagation = Propagation.MANDATORY)
- 当前有事务:加入当前事务。
- 当前无事务:抛异常
IllegalTransactionStateException。
特点:
- 强制要求调用方已经开启事务。
- 自己不会新建事务。
场景:某些内部核心方法,必须由外层事务统一控制,不允许被随意单独调用。
4. REQUIRES_NEW:总是新建独立事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
- 当前有事务:挂起当前事务,新建一个独立事务。
- 当前无事务:新建一个事务。
特点:
- 内外事务相互独立。
- 内层提交后,即使外层回滚,内层已提交的数据通常不会回滚。
- 内层回滚后,如果异常被外层捕获,外层可以继续提交。
- 注意数据库锁:外层事务可能持有锁,内层新事务再操作同一行,可能死锁或等待。
场景:
- 日志记录、审计记录。
- 失败也不影响主流程的独立操作。
- 主业务回滚,但某些记录必须保留。
示例:
@Transactional
public void createOrder() {
orderMapper.insert(order);
try {
logService.saveLog(); // REQUIRES_NEW
} catch (Exception e) {
// 日志失败不影响订单主事务
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog() {
logMapper.insert(log);
}
5. NOT_SUPPORTED:以非事务方式执行,挂起当前事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
- 当前有事务:挂起当前事务,自己以非事务方式执行。
- 当前无事务:以非事务方式执行。
特点:
- 自己不在事务中运行。
- 适合不希望被事务包裹的操作。
- 如果操作失败,不会触发当前事务回滚,但当前事务被挂起期间,数据库连接和锁可能仍然存在。
场景:
- 发送消息、调用远程接口、耗时操作。
- 不希望长时间占用事务。
6. NEVER:必须无事务,否则报错
@Transactional(propagation = Propagation.NEVER)
- 当前有事务:抛异常
IllegalTransactionStateException。 - 当前无事务:以非事务方式执行。
特点:
- 和
MANDATORY相反。 - 强制方法不能在事务中运行。
场景:
- 某些工具方法、校验方法,明确禁止事务。
- 避免被外层事务误包裹。
7. NESTED:嵌套事务,基于保存点 Savepoint
@Transactional(propagation = Propagation.NESTED)
- 当前有事务:创建嵌套事务,本质是设置一个保存点
Savepoint。 - 当前无事务:新建事务,类似
REQUIRED。
特点:
- 内层回滚只会回滚到保存点,外层可以继续。
- 外层回滚会连带内层一起回滚。
- 内层提交并不是真正提交,只是释放保存点,最终仍由外层决定提交或回滚。
- 需要底层资源支持保存点,例如 JDBC 的
DataSourceTransactionManager。 - JPA、Hibernate、JTA 等是否支持,要看具体实现和方言。
- 与
REQUIRES_NEW最大区别:NESTED不挂起外层事务,是同一个物理事务中的保存点;REQUIRES_NEW是独立的新事务。
示例:
@Transactional
public void outer() {
// 业务 A
try {
innerService.inner();
} catch (Exception e) {
// 内层回滚到保存点,外层可以继续
}
// 业务 B
}
@Transactional(propagation = Propagation.NESTED)
public void inner() {
// 业务 C
}
七种传播机制速查表
| 传播行为 | 当前有事务 | 当前无事务 | 是否挂起当前事务 | 典型场景 |
|---|---|---|---|---|
| REQUIRED | 加入当前事务 | 新建事务 | 否 | 默认,大多数增删改 |
| SUPPORTS | 加入当前事务 | 非事务执行 | 否 | 查询 |
| MANDATORY | 加入当前事务 | 抛异常 | 否 | 强制要求外层有事务 |
| REQUIRES_NEW | 挂起当前事务,新建独立事务 | 新建事务 | 是 | 日志、审计、独立业务 |
| NOT_SUPPORTED | 挂起当前事务,非事务执行 | 非事务执行 | 是 | 远程调用、耗时操作 |
| NEVER | 抛异常 | 非事务执行 | 否 | 禁止事务 |
| NESTED | 创建保存点,嵌套事务 | 新建事务 | 否 | 部分回滚,外层可继续 |
REQUIRED、REQUIRES_NEW、NESTED 的区别
这是面试和实战最常问的。
REQUIRED
内外同一个事务:
- 内层异常,通常整体回滚。
- 外层异常,整体回滚。
- 不能做到“内层失败但外层继续提交”。
REQUIRES_NEW
内外两个独立事务:
- 内层异常回滚,外层捕获后可以继续。
- 内层成功提交后,外层回滚不影响内层。
- 会挂起外层事务,可能产生锁竞争。
NESTED
同一个物理事务中的嵌套事务,基于保存点:
- 内层回滚到保存点,外层可以继续。
- 外层回滚,内层也会回滚。
- 内层不是真正独立提交。
- 依赖底层保存点支持。
Spring Boot 使用示例
@Service
public class OrderService {
@Autowired
private LogService logService;
@Transactional(rollbackFor = Exception.class)
public void createOrder() {
// 主业务
// orderMapper.insert(...);
try {
logService.saveLog();
} catch (Exception e) {
// 日志失败不影响订单
}
}
}
@Service
public class LogService {
@Transactional(
propagation = Propagation.REQUIRES_NEW,
rollbackFor = Exception.class
)
public void saveLog() {
// logMapper.insert(...);
}
}
注意:OrderService 必须通过 Spring 代理调用 LogService,传播行为才生效。
常见坑
-
默认只对 RuntimeException 和 Error 回滚
@Transactional(rollbackFor = Exception.class)受检异常默认不回滚。
-
同类内部调用事务失效
public void a() { this.b(); // b 上的 @Transactional 不生效 }因为没走 Spring 代理。
-
异常被吞掉,事务不会回滚
try { // 业务 } catch (Exception e) { // 没抛出,也没标记回滚 } -
多线程事务不传播
Spring 事务基于 ThreadLocal,新线程不会继承当前事务。 -
NESTED 不一定所有数据库/ORM 都支持
它依赖保存点,JDBC 支持较好,JPA/JTA 要看具体实现。 -
REQUIRES_NEW 可能死锁
外层事务未提交,持有数据库锁,内层新事务操作同一数据可能等待或死锁。 -
数据库引擎要支持事务
例如 MySQL 的 MyISAM 不支持事务。
选择建议
- 大多数业务:用默认
REQUIRED。 - 查询:
SUPPORTS + readOnly = true。 - 必须在外层事务中运行:
MANDATORY。 - 独立提交、不影响主事务:
REQUIRES_NEW。 - 不想被事务包裹:
NOT_SUPPORTED。 - 禁止事务:
NEVER。 - 需要部分回滚、外层可继续:
NESTED,但要确认底层支持保存点。
一句话总结:
REQUIRED 是默认同生共死;REQUIRES_NEW 是另起炉灶;NESTED 是保存点回滚;SUPPORTS 是随缘;MANDATORY 是必须有;NOT_SUPPORTED 是挂起不用;NEVER 是绝对不要。
