Spring 中 REQUIRED 事务的回滚机制详解
在 Spring 的事务管理中,REQUIRED
是最常用也是默认的事务传播属性。很多开发者在使用时会遇到一个常见的困惑:为什么内部方法抛出的异常即便被外层捕获了,事务还是会整体回滚? 本文将深入剖析 REQUIRED
下的事务回滚机制及其背后的原理。
1. REQUIRED 的定义
- 默认传播属性。
- 如果当前存在事务:加入当前事务。
- 如果没有事务:创建一个新事务。
这意味着调用链上的所有方法都运行在同一个物理事务里,彼此之间没有隔离。只要其中一个方法触发回滚条件,整个事务都会被撤销。
2. REQUIRED 下的回滚机制
2.1 异常触发回滚
Spring 默认回滚规则:
- 运行时异常(
RuntimeException
)和错误(Error
):触发回滚。 - 受检异常(
CheckedException
):不会触发回滚,除非在@Transactional(rollbackFor = Exception.class)
中显式指定。
2.2 回滚状态传播
假设调用链如下:
java
@Transactional(propagation = Propagation.REQUIRED)
public void outer() {
try {
inner();
} catch (Exception e) {
// 异常被捕获
}
}
@Transactional(propagation = Propagation.REQUIRED)
public void inner() {
throw new RuntimeException("内部错误");
}
执行过程:
outer()
启动时创建事务。inner()
加入同一事务并抛出异常。- Spring 的事务拦截器捕获异常 → 标记当前事务为 rollback-only。
- 即便外层
outer()
捕获了异常,rollback-only 标记不会自动清除。 - 最终事务提交时,Spring 检查到 rollback-only → 调用
rollback()
而不是commit()
。
因此,捕获异常并不能阻止事务回滚。
3. 为什么捕获异常也会回滚?
原因在于 Spring 的事务是基于 事务状态标记 而不是你的 try-catch
来决定的:
- 方法抛出异常 →
TransactionInterceptor
判断是否需要回滚。 - 如果符合规则 →
status.setRollbackOnly()
。 - 外层即便捕获异常,也只是业务层逻辑"消化"了异常,但事务状态已经不可提交。
最终由事务管理器在 commit()
阶段检查 rollback-only 标记,决定整体回滚。
4. 如何避免"误回滚"?
如果确实需要内部异常不影响外部事务,可以考虑以下方式:
4.1 修改回滚规则
java
@Transactional(noRollbackFor = RuntimeException.class)
public void inner() {
throw new RuntimeException("不会触发回滚");
}
但这种方式风险极高,可能提交错误数据。
4.2 手动清理事务状态(不推荐)
java
catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(false);
}
此方式非常危险,通常不建议。
4.3 使用其他传播属性(推荐)
REQUIRES_NEW
:为内部方法开启新事务,失败只影响内部,不影响外部。NESTED
:使用保存点,内部失败可回滚到保存点,外部事务继续。
这两种方式才是更优雅的解决方案。
5. Spring 内部执行流程
核心逻辑在 TransactionAspectSupport.invokeWithinTransaction()
:
- 获取或创建事务。
- 执行目标方法。
- 捕获异常 → 判断是否需要回滚 → 设置 rollback-only。
- 方法结束 → 根据状态决定
commit()
还是rollback()
。
6. 总结
REQUIRED
下,所有方法共享一个事务。- 任意方法异常触发 rollback-only → 整体回滚。
- 捕获异常≠事务安全,Spring 根据事务状态决定提交还是回滚。
- 如果需要更细粒度的控制,应使用
REQUIRES_NEW
或NESTED
。
一句话总结 :在 Spring 的 REQUIRED
模式下,事务是一荣俱荣、一损俱损的整体,内部异常即便被捕获也会让整个事务回滚,真正想做到"局部回滚"需要换传播属性来实现。