Spring 事务传播行为
一、什么是事务传播行为
事务传播行为定义了一个事务方法被另一个事务方法调用时,事务如何传播,即被调用方法是在调用方的事务中执行,还是新开事务,还是不用事务。
Spring 共定义了 7 种传播行为 ,定义在 Propagation 枚举中。
二、七种传播行为详解
| 传播行为 | 说明 | 是否依赖已有事务 | 是否新建事务 |
|---|---|---|---|
| REQUIRED | 默认行为。有事务则加入,没有则新建 | ✅ | 可新建 |
| SUPPORTS | 有事务就加入,没有就以非事务方式运行 | ✅ | ❌ |
| MANDATORY | 必须在已有事务中执行,没有则抛异常 | ✅ | ❌ |
| REQUIRES_NEW | 不管外部有无事务,总是新建一个独立事务,原事务挂起 | ❌ | ✅ |
| NOT_SUPPORTED | 以非事务方式运行,外部有事务则挂起 | ❌ | ❌ |
| NEVER | 必须以非事务方式运行,外部有事务则抛异常 | ❌ | ❌ |
| NESTED | 在已有事务中创建嵌套子事务(Savepoint 机制) | ✅ | ✅(子事务) |
各传播行为详细说明
-
REQUIRED(默认)
- 最常用,覆盖 90% 场景
- 有事务则融入,没有则创建新事务
- 示例:订单创建调库存扣减,同事务同成功同失败
-
SUPPORTS
- 有事务跟着跑,没有就裸奔(非事务运行)
- 适合可事务可不事务的只读操作
- 示例:查询操作,有人调时有事务保一致性,没有也无所谓
-
MANDATORY
- 必须在已有事务中调用,否则抛
IllegalTransactionStateException - 适合调用方必须提供事务上下文的严格场景
- 必须在已有事务中调用,否则抛
-
REQUIRES_NEW
- 不管外部有没有事务,总是新开独立事务
- 外部原事务被挂起,等新事务完成后恢复
- 内外事务完全独立,互不影响
- 示例:审计日志(主流程失败,日志也必须独立提交)
-
NOT_SUPPORTED
- 明确不要事务,有事务就挂起
- 适合对一致性无要求的轻量操作
-
NEVER
- 绝对不能有事务,外部有事务直接抛异常
- 适合严格要求非事务运行的场景
-
NESTED
- 在当前事务内开嵌套子事务,通过 JDBC Savepoint 实现
- 子事务回滚只回滚到 Savepoint,不影响外层
- 外层回滚会连带子事务一起回滚(与 REQUIRES_NEW 的关键区别)
- 仅适用于 JDBC 单资源事务,JTA 分布式事务不支持
- 示例:批量导入,每条一个子事务,某条失败只回滚那条
三、REQUIRES_NEW vs NESTED 核心对比
| 对比维度 | REQUIRES_NEW | NESTED |
|---|---|---|
| 事务关系 | 完全独立的新事务 | 外层事务的子事务 |
| 外层回滚 | 不影响内层(已提交) | 会连带回滚子事务 |
| 子事务回滚 | 不影响外层 | 不影响外层(回滚到 Savepoint) |
| 实现机制 | 新建数据库物理连接/事务 | Savepoint 机制 |
| 分布式事务支持 | 支持 | 不支持(仅 JDBC 单资源) |
四、常用三种场景
1. REQUIRED(绝大多数场景)
订单创建 → 库存扣减,两个操作必须在同一事务中,要么一起成功,要么一起回滚。
java
@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
// 订单落库
inventoryService.deduct(); // REQUIRED 自动加入当前事务
}
}
2. REQUIRES_NEW(必须独立提交)
不管主流程成败,日志必须写入。
java
@Service
public class AuditService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordLog() {
// 转账即使失败,这条日志也必须写进去
}
}
3. NESTED(部分失败可接受)
批量导入 1000 条数据,每条一个嵌套子事务,某条失败只回滚那条,其他继续。外层事务可设容错阈值,失败太多时整体回滚。
java
@Service
public class ImportService {
@Transactional(propagation = Propagation.REQUIRED)
public void batchImport(List<Data> list) {
int failCount = 0;
for (Data data : list) {
try {
importOne(data); // NESTED 子事务
} catch (Exception e) {
failCount++;
if (failCount > threshold) {
throw new RuntimeException("失败过多,整体回滚");
}
}
}
}
@Transactional(propagation = Propagation.NESTED)
public void importOne(Data data) {
// 单条导入逻辑
}
}
五、总结
- REQUIRED:默认,大锅饭模式,大多数场景够用
- REQUIRES_NEW:独立出去单干,适合日志等必须落库的操作
- NESTED:外层兜底,内部分批,适合批处理
- 其他四种(SUPPORTS、MANDATORY、NOT_SUPPORTED、NEVER)用得较少,视具体需求选用