★ Spring 事务传播行为详解
结论先行:传播行为解决"事务方法调用另一个事务方法时,事务如何扩散"的问题,共 7 种(TransactionDefinition/Propagation 枚举),默认 REQUIRED:外层有事务就加入、没有就新建。最高频的对比是 REQUIRES_NEW 与 NESTED:前者挂起外层开一个全新独立事务(内层回滚不影响外层,外层回滚也不牵连已提交的内层);后者用数据库 Savepoint 做嵌套(内层回滚只回退到保存点,整体仍是外层一个事务)。
一、7 种传播行为速查
| 传播行为 | 外层有事务时 | 外层无事务时 | 典型场景 |
|---|---|---|---|
| REQUIRED(默认) | 加入外层事务 | 新建事务 | 绝大多数业务方法 |
| REQUIRES_NEW | 挂起外层,另开新事务 | 新建事务 | 审计日志、发消息(必须独立成败) |
| SUPPORTS | 加入 | 非事务执行 | 有则用之、无则不用 |
| NOT_SUPPORTED | 挂起外层,非事务执行 | 非事务执行 | 长查询不占事务连接 |
| MANDATORY | 加入 | 抛 IllegalTransactionStateException | 强制调用方开事务 |
| NEVER | 抛异常 | 非事务执行 | 禁止在事务内执行 |
| NESTED | Savepoint 嵌套子事务 | 新建事务 | 批处理中单条失败可回退该项 |
二、代码示例
@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void writeAuditLog(Order o) { } // 独立事务:外层回滚不影响日志落库
@Transactional(propagation = Propagation.NESTED)
public void saveOneItem(Item i) { } // Savepoint:本条失败只回退本条
}
三、高频追问要点
- REQUIRES_NEW vs NESTED:前者是两个独立事务(各自提交/回滚,外层 catch 后仍可继续);后者同一事务内 Savepoint(内层回滚后外层可捕获继续,外层最终回滚则内层也滚);NESTED 依赖数据库 savepoint 支持(MySQL InnoDB 支持);
- REQUIRED 的"rollback-only"陷阱:内外层同一个事务,内层回滚会把整个事务标记 rollback-only,即使外层 catch 了异常,外层提交时仍抛 UnexpectedRollbackException → 整体回滚;
- 自调用时传播行为不生效(见 13/16:同类 this 调用不经过代理)。
常见追问 / 记忆点
- 追问 1:REQUIRED 与 REQUIRES_NEW 最常用在什么对比场景?答:业务主流程用 REQUIRED;"日志必须写成功/失败不影响主流程"用 REQUIRES_NEW。
- 追问 2:为什么 NESTED 不是真"子事务"?答:底层就是 Savepoint 回退点,没有独立的提交/回滚边界。
- 记忆点:先记铁三角 REQUIRED / REQUIRES_NEW / NESTED,再补其余四个;"加入、新开、挂起、嵌套"各归其位。