事务传播、隔离与回滚规则
传播方式决定方法进入时如何处理已有事务,隔离级别决定数据库并发读写可见性,回滚规则决定异常如何影响事务结果。这三组配置作用在不同层面,需要分别验证。
1. REQUIRED 复用同一个物理事务
默认 PROPAGATION_REQUIRED 在已有事务时加入,没有时新建。内外方法各自是逻辑事务范围,但共享同一个物理事务。
内层把事务标记为 rollback-only 后,即使外层捕获异常并正常返回,最终提交也会回滚,并可能抛出 UnexpectedRollbackException。这避免调用方误以为提交成功。
2. REQUIRES_NEW 使用独立物理事务
已有事务被挂起,内层取得自己的连接和事务:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordAudit(AuditEvent event) {}
内层可以独立提交或回滚,并在完成后释放自己的锁。外层仍持有资源,内层还要再向连接池申请连接;并发线程都这样做时,池大小不足会互相等待。
它适合确实要求独立提交的审计等场景,但也意味着外层失败时内层结果仍保留。
3. NESTED 依赖同一事务的保存点
PROPAGATION_NESTED 通常在一个物理 JDBC 事务中建立 savepoint。内层失败可以回滚到保存点,让外层决定是否继续;外层最终回滚时,内层变化仍会一起回滚。
它不等同于独立事务,并非所有事务管理器都支持。Spring 文档明确指出典型支持来自 JDBC DataSourceTransactionManager。
4. 其他传播方式表达调用前置条件
| 传播方式 | 已有事务 | 没有事务 |
|---|---|---|
SUPPORTS | 加入 | 非事务执行 |
MANDATORY | 加入 | 抛异常 |
NOT_SUPPORTED | 挂起 | 非事务执行 |
NEVER | 抛异常 | 非事务执行 |
是否真的能挂起取决于事务管理器。大多数业务方法使用少数几种传播方式,复杂组合需要集成测试证明。
5. 隔离级别由新物理事务落实
Isolation.DEFAULT 使用数据库默认值。指定隔离级别通常只在当前调用实际创建新事务时生效;加入已有事务时,内层不能凭注解把同一物理事务换成另一隔离级别。
可以启用严格验证让不兼容声明尽早失败。隔离级别解决数据库并发可见性,不处理远程服务或缓存的一致性。
6. 默认回滚 RuntimeException 与 Error
声明式事务默认对未处理的 RuntimeException 和 Error 回滚,对普通受检异常不自动回滚。业务需要时显式声明:
@Transactional(rollbackFor = PaymentException.class)
更重要的是让异常离开代理调用。目标方法内部捕获后返回成功,拦截器看不到失败;如果需要回滚,应继续抛出或显式设置 rollback-only,并让调用方知道结果。
7. 常见问题
7.1 内层 REQUIRES_NEW 提交后,外层回滚会撤销它吗
不会,它们是独立物理事务。业务必须接受内层记录单独存在,或设计后续补偿。
7.2 NESTED 内层回滚以后外层一定能提交吗
不一定。外层可以继续,但还要看异常处理、数据库错误是否使整个事务失效,以及事务管理器是否支持保存点。
8. 面试题
8.1 REQUIRES_NEW 与 NESTED 有什么区别
出现公司:阿里云、美团
考察重点
- 独立物理事务与同事务保存点。
- 外层回滚的影响。
- 连接池与事务管理器支持。
相关内容:第 2 节“REQUIRES_NEW 使用独立物理事务”和第 3 节“NESTED 依赖同一事务的保存点”。
参考回答
REQUIRES_NEW 挂起外层并创建独立物理事务,内层提交后不受外层回滚影响,但会额外占用连接。NESTED 通常在同一 JDBC 事务中建立 savepoint,内层可以局部回滚,外层最终回滚仍会撤销全部变化。
两者还受 TransactionManager 能力限制。REQUIRES_NEW 要评估连接池耗尽,NESTED 要确认保存点支持,不能只按注解名称选择。