事务回滚机制 - rollbackFor/noRollbackFor 与回滚源码解析
1. 概述
Spring 的声明式事务管理通过 @Transactional 注解为开发者提供了简洁的事务控制能力。事务回滚是其中最关键的一环——当业务方法执行过程中抛出异常时,Spring 需要决定是提交事务还是回滚事务。本文将深入剖析 Spring 事务回滚的完整机制,包括回滚规则的配置方式、默认回滚策略、底层源码实现以及部分回滚(Savepoint)原理。
2. @Transactional 回滚规则属性
@Transactional 注解提供了四个用于控制回滚规则的属性:
| 属性 | 类型 | 说明 |
|---|---|---|
rollbackFor | Class<? extends Throwable>[] | 触发回滚的异常类数组 |
rollbackForClassName | String[] | 触发回滚的异常类名数组 |
noRollbackFor | Class<? extends Throwable>[] | 不触发回滚的异常类数组 |
noRollbackForClassName | String[] | 不触发回滚的异常类名数组 |
2.1 rollbackFor / rollbackForClassName
指定哪些异常应当触发事务回滚:
@Transactional(rollbackFor = Exception.class)
public void createUser(User user) throws Exception {
// 任何 Exception(含 checked exception)都会触发回滚
userDao.insert(user);
}@Transactional(rollbackForClassName = {"java.io.IOException", "java.sql.SQLException"})
public void uploadFile(File file) throws IOException, SQLException {
// 仅 IOException 和 SQLException 触发回滚
fileService.save(file);
}2.2 noRollbackFor / noRollbackForClassName
指定哪些异常不应触发回滚,即使这些异常本应导致回滚:
@Transactional(noRollbackFor = BusinessWarningException.class)
public void processOrder(Order order) {
// BusinessWarningException 不会触发回滚
// 即使它是 RuntimeException 的子类
orderService.process(order);
}@Transactional(noRollbackForClassName = {"com.example.BusinessWarningException"})
public void handlePayment(Payment payment) {
paymentService.pay(payment);
}3. 默认回滚策略
3.1 默认行为
Spring 事务管理的默认回滚策略是:仅回滚 RuntimeException 和 Error 及其子类,不回滚 checked Exception。
// 默认情况下:
// - 抛出 NullPointerException(RuntimeException 子类)→ 回滚
// - 抛出 SQLException(checked Exception)→ 不回滚,提交事务
@Transactional
public void defaultRollbackPolicy() throws SQLException {
userDao.insert(user); // 成功插入
logDao.insert(log); // 抛出 SQLException
// 事务会提交?还是会回滚?
// 答案:提交!user 被持久化,log 写入失败
}这一设计的核心理念是:Checked Exception 代表调用方可预期的业务异常,通常业务代码已经准备好了补偿逻辑,因此不应该回滚整个事务;而 RuntimeException 和 Error 代表不可预期的系统级错误,应当回滚事务以保证数据一致性。
3.2 默认策略的源码依据
该默认行为定义在 TransactionAttribute 接口的默认实现中,具体由 DefaultTransactionAttribute 或 RuleBasedTransactionAttribute 负责判断。
// DefaultTransactionAttribute.java (Spring 5.3.x)
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}4. 自定义回滚规则的设置方式与优先级
4.1 设置方式
Spring 支持两种方式来定义回滚规则:
方式一:注解属性(最常见)
@Transactional(rollbackFor = Exception.class, noRollbackFor = BusinessWarningException.class)
public void mixedRules() {
// ...
}方式二:编程式 TransactionAttribute
@Bean
public TransactionAttributeSource transactionAttributeSource() {
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
RuleBasedTransactionAttribute attr = new RuleBasedTransactionAttribute();
attr.setRollbackRules(List.of(
new RollbackRuleAttribute(Exception.class),
new NoRollbackRuleAttribute(BusinessWarningException.class)
));
source.addTransactionalMethod("transfer*", attr);
return source;
}4.2 优先级规则
当存在多条回滚规则时,Spring 按照以下优先级判断:
noRollbackFor/noRollbackForClassName优先于rollbackFor/rollbackForClassName- 子类匹配优先于父类匹配
- 精确匹配优先于通配匹配
判断逻辑在 RuleBasedTransactionAttribute 中实现:
// RuleBasedTransactionAttribute.java (Spring 5.3.x)
public boolean rollbackOn(Throwable ex) {
if (this.rollbackRules != null) {
RollbackRuleAttribute winner = null;
int deepest = Integer.MAX_VALUE;
for (RollbackRuleAttribute rule : this.rollbackRules) {
int depth = rule.getDepth(ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule;
}
}
if (winner != null) {
// noRollbackRuleAttribute 的 getDepth() 返回正数,
// 但它的 rollbackOn() 始终返回 false
return !(winner instanceof NoRollbackRuleAttribute);
}
}
// 没有匹配到任何自定义规则,回退到默认策略
return super.rollbackOn(ex);
}优先级示例:
@Transactional(
rollbackFor = Exception.class,
noRollbackFor = {IllegalArgumentException.class, BusinessWarningException.class}
)
public void priorityTest() {
// 抛出 IllegalArgumentException
// → 匹配 noRollbackFor(IllegalArgumentException),不回滚
// → 虽然也匹配 rollbackFor(Exception 父类),但 noRollbackFor 优先级更高
throw new IllegalArgumentException("test");
}规则的深度(getDepth)计算逻辑基于异常的继承层次:
// RollbackRuleAttribute.getDepth()
public int getDepth(Throwable ex) {
return getDepth(ex.getClass(), 0);
}
private int getDepth(Class<?> exceptionClass, int depth) {
if (exceptionClass == this.exceptionType) {
return depth; // 精确匹配,depth = 0
}
if (exceptionClass == Throwable.class) {
return -1; // 到达继承树顶端仍未匹配
}
// 向父类递归查找,depth + 1
return getDepth(exceptionClass.getSuperclass(), depth + 1);
}5. 回滚时机
Spring 事务回滚发生在两个关键时间点:
5.1 异常抛出时——completeTransactionAfterThrowing
当被 @Transactional 修饰的方法抛出异常时,Spring AOP 拦截器会捕获该异常,并调用 TransactionAspectSupport.completeTransactionAfterThrowing() 方法。这是回滚的主要时机。
// TransactionAspectSupport.java (Spring 5.3.x) — 简化逻辑
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
// 规则判定需要回滚 → 执行回滚
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
} catch (TransactionSystemException ex2) {
// 回滚失败时的处理
throw ex2;
}
} else {
// 规则判定不需要回滚 → 仍然提交
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
} catch (TransactionSystemException ex2) {
// 提交失败时的处理
throw ex2;
}
}
}
}5.2 业务方法结束后——commit
如果业务方法正常执行完毕(未抛出异常),则在 commit 时检查 rollbackOnly 标记。这是回滚的次要时机。
// TransactionAspectSupport.java — 方法正常返回时的提交逻辑
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}在 commit 内部,会检测 rollbackOnly 标记,如果被标记为仅回滚,则即便业务方法正常结束,事务仍然会回滚。
6. 回滚源码分析
6.1 TransactionAspectSupport.completeTransactionAfterThrowing() 完整精读
这是 Spring 事务回滚的入口方法,位于 org.springframework.transaction.interceptor.TransactionAspectSupport 中。
// TransactionAspectSupport.java (Spring 5.3.x)
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
// 步骤1:判断是否存在活跃的事务信息
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
// 步骤2:判断该异常是否触发回滚——调用 rollbackOn() 规则判定
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
// 步骤3:执行回滚
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
} catch (TransactionSystemException ex2) {
// 步骤4:回滚过程抛出异常时的兜底处理
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
} catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
} else {
// 步骤5:异常不触发回滚 → 尝试提交
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
} catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
} catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}执行流程图解:
业务方法抛出异常
│
▼
completeTransactionAfterThrowing(txInfo, ex)
│
├── txInfo == null || txInfo.getTransactionStatus() == null ?
│ └── 是 → 直接返回,不做任何事
│
├── transactionAttribute.rollbackOn(ex) ?
│ │
│ ├── 是 → transactionManager.rollback(status)
│ │ └── 发生异常 → 包装并抛出 TransactionSystemException
│ │
│ └── 否 → transactionManager.commit(status)
│ └── 发生异常 → 包装并抛出 TransactionSystemException
│
▼
结束6.2 DefaultTransactionStatus.setRollbackOnly() 的标记机制
DefaultTransactionStatus 是 TransactionStatus 接口的核心实现,它内部维护了一个 rollbackOnly 布尔标记。
// DefaultTransactionStatus.java (Spring 5.3.x)
public class DefaultTransactionStatus extends AbstractTransactionStatus {
@Nullable
private final Object transaction;
private final boolean newTransaction;
private final boolean newSynchronization;
private final boolean readOnly;
private final boolean debug;
@Nullable
private final Object suspendedResources;
// 核心:回滚标记
// private boolean rollbackOnly; // 继承自 AbstractTransactionStatus
}setRollbackOnly() 的定义在 AbstractTransactionStatus 中:
// AbstractTransactionStatus.java (Spring 5.3.x)
public class AbstractTransactionStatus implements TransactionStatus {
private boolean rollbackOnly = false;
@Nullable
private Set<TransactionExecutionListener> executionListeners;
// 省略其他属性和方法...
@Override
public void setRollbackOnly() {
this.rollbackOnly = true;
// 通知事务执行监听器
if (this.executionListeners != null) {
for (TransactionExecutionListener listener : this.executionListeners) {
try {
listener.afterRollbackOnlySet(this);
} catch (Throwable ex) {
// 监听器异常不应影响事务流程
}
}
}
}
@Override
public boolean isRollbackOnly() {
return this.rollbackOnly;
}
}触发 setRollbackOnly() 的场景:
场景一:用户主动调用 TransactionStatus.setRollbackOnly()
@Transactional
public void businessMethod() {
// ... 业务逻辑
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// 方法正常返回,但事务会在 commit 时检测到 rollbackOnly 标记并回滚
}场景二:嵌套事务(Propagation.REQUIRES_NEW)内部异常
当嵌套事务(内层事务)抛出异常时,外层事务会被标记为 rollbackOnly:
@Transactional
public void outerMethod() {
innerMethod(); // 内层方法抛出异常并回滚
// 继续执行... 但事务已被标记为 rollbackOnly
// commit 时检测到标记 → 整体回滚
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerMethod() {
throw new RuntimeException("inner error");
}rollbackOnly 标记在 commit 时的检测:
// AbstractPlatformTransactionManager.processCommit() 中的关键检查
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
// ... 前置准备
// 核心检测:如果被标记为 rollbackOnly,则转为执行回滚
if (status.isRollbackOnly()) {
if (status.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
// 转为回滚流程
processRollback(status, false);
return;
}
// ... 正常提交流程
}6.3 AbstractPlatformTransactionManager.processRollback() 执行回滚的完整流程
processRollback() 是 Spring 事务回滚的核心方法,位于 AbstractPlatformTransactionManager 中。
// AbstractPlatformTransactionManager.java (Spring 5.3.x)
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
// 步骤1:触发回滚前的钩子方法
triggerBeforeCompletion(status);
// 步骤2:判断是否有 Savepoint(部分回滚场景)
if (status.hasSavepoint()) {
// ---- 部分回滚:回滚到 Savepoint ----
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
// 委托给底层事务管理器执行 Savepoint 回滚
status.rollbackToHeldSavepoint();
}
// 步骤3:判断是否是独立的新事务
else if (status.isNewTransaction()) {
// ---- 完整回滚:新事务的完整回滚 ----
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
// 调用底层平台事务管理的 doRollback()
doRollback(status);
}
// 步骤4:存在外层事务,且非 Savepoint 场景
else if (status.hasTransaction()) {
// ---- 参与外部事务:标记外部事务为 rollbackOnly ----
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
// 标记外层事务为 rollbackOnly,交由外层事务的提交/回滚流程处理
doSetRollbackOnly(status);
unexpectedRollback = false; // 已处理,不视为意外回滚
}
// 步骤5:没有底层事务(仅同步)
else {
if (status.isDebug()) {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
}
// 步骤6:触发回滚完成后的钩子方法
// triggerAfterCompletion 会清理资源、恢复挂起的事务等
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
} catch (RuntimeException | Error ex) {
// 回滚过程中自身抛出异常
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
// 步骤7:如果回滚是"意外的",抛出 UnexpectedRollbackException
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
} finally {
// 步骤8:清理事务资源
cleanupAfterCompletion(status);
}
}回滚流程决策树:
processRollback(status, unexpected)
│
├── 1. triggerBeforeCompletion(status)
│ └── 回调所有 TransactionSynchronization.beforeCompletion()
│
├── 2. status.hasSavepoint() ?
│ └── 是 → status.rollbackToHeldSavepoint() ← 部分回滚
│
├── 3. status.isNewTransaction() ?
│ └── 是 → doRollback(status) ← 完整回滚
│
├── 4. status.hasTransaction() ?
│ └── 是 → doSetRollbackOnly(status) ← 标记外层事务
│
├── 5. 无事务 → 仅日志记录
│
├── 6. triggerAfterCompletion(status, STATUS_ROLLED_BACK)
│
├── 7. unexpected ? → 抛出 UnexpectedRollbackException
│
└── 8. cleanupAfterCompletion(status)doRollback() 的典型实现(以 DataSourceTransactionManager 为例):
// DataSourceTransactionManager.java (Spring 5.3.x)
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
// 核心:调用 JDBC Connection.rollback()
con.rollback();
} catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}cleanupAfterCompletion() 的清理工作:
// AbstractPlatformTransactionManager.java (Spring 5.3.x)
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
// 1. 设置事务状态为已完成
status.setCompleted();
// 2. 如果是新的事务同步,则清理同步资源
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
// 3. 如果之前挂起了资源,则恢复
if (status.isNewTransaction()) {
// 恢复挂起的数据库连接等资源
doCleanupAfterCompletion(status.getTransaction());
}
// 4. 恢复挂起的资源(如果存在)
if (status.getSuspendedResources() != null) {
if (status.isDebug()) {
logger.debug("Resuming suspended transaction after completion");
}
resumeTransaction(status.getSuspendedResources(), status.isNewSynchronization());
}
}7. 部分回滚(Savepoint)的实现原理
7.1 Savepoint 的概念
Savepoint(保存点)是 JDBC 3.0 引入的特性,允许在事务内部设置一个中间点。当回滚发生时,可以选择回滚到某个 Savepoint 而非回滚整个事务,从而保留 Savepoint 之前的操作。
Spring 利用 JDBC Savepoint 实现了**嵌套事务的「部分回滚」**能力,主要体现在 Propagation.NESTED 传播行为中。
7.2 Propagation.NESTED 与 Savepoint
@Transactional
public void outerMethod() {
userDao.insert(user); // 操作1:插入用户
innerMethod(); // 操作2:嵌套子事务
orderDao.createOrder(order); // 操作3:创建订单
}
@Transactional(propagation = Propagation.NESTED)
public void innerMethod() {
logDao.save(log); // 插入日志
if (someCondition) {
throw new RuntimeException("内层异常"); // 仅回滚到 Savepoint
}
}当 innerMethod() 抛出异常时:
- Spring 回滚到
innerMethod()开始前设置的 Savepoint userDao.insert(user)的操作保留(在 Savepoint 之前)logDao.save(log)的操作撤销(在 Savepoint 之后)orderDao.createOrder(order)继续正常执行
7.3 Savepoint 的创建与回滚源码
创建 Savepoint:
当 PROPAGATION_NESTED 要求创建嵌套事务时,Spring 不会创建真正的物理子事务,而是在当前连接上设置 Savepoint:
// DataSourceTransactionManager.java (Spring 5.3.x)
protected Object doSavepoint() {
Connection con = getConnection();
try {
// 在 JDBC 连接上创建 Savepoint
return con.setSavepoint();
} catch (SQLException ex) {
throw new TransactionSystemException("Could not set JDBC savepoint", ex);
}
}回滚到 Savepoint:
// DataSourceTransactionManager.java (Spring 5.3.x)
protected void doRollbackToSavepoint(Object savepoint) {
Connection con = getConnection();
try {
// 回滚到指定的 Savepoint
con.rollback((Savepoint) savepoint);
// 释放 Savepoint 资源
con.releaseSavepoint((Savepoint) savepoint);
} catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex);
}
}释放 Savepoint:
// DataSourceTransactionManager.java (Spring 5.3.x)
protected void doReleaseSavepoint(Object savepoint) {
Connection con = getConnection();
try {
con.releaseSavepoint((Savepoint) savepoint);
} catch (SQLException ex) {
// 有些数据库不支持显式释放 Savepoint,忽略此异常
logger.debug("Could not release JDBC savepoint", ex);
}
}7.4 Savepoint 回滚在 processRollback 中的位置
回顾 processRollback() 中的决策逻辑:
// 步骤2:判断是否有 Savepoint
if (status.hasSavepoint()) {
// 回滚到 Savepoint → 部分回滚
status.rollbackToHeldSavepoint();
}
// 步骤3:判断是否是独立的新事务
else if (status.isNewTransaction()) {
// 完整回滚
doRollback(status);
}hasSavepoint() 检查 DefaultTransactionStatus 中保存的 Savepoint 引用:
// DefaultTransactionStatus.java
public boolean hasSavepoint() {
return (this.savepoint != null);
}7.5 Savepoint 的适用条件与限制
| 条件 | 说明 |
|---|---|
| 底层数据源 | 需要支持 JDBC 3.0 Savepoint 特性(主流关系型数据库均支持) |
| 全局事务 | JTA 全局事务不支持 Savepoint 机制 |
| 传播行为 | 仅 Propagation.NESTED 使用 Savepoint 实现部分回滚 |
REQUIRES_NEW | 采用物理独立事务而非 Savepoint |
注意
Propagation.REQUIRES_NEW 与 Propagation.NESTED 的区别:
REQUIRES_NEW:挂起当前事务,创建全新的物理事务,内外事务完全独立NESTED:在当前事务中设置 Savepoint,内外事务共享同一个物理连接,内层回滚不影响外层已执行的操作
8. 总结
Spring 事务回滚机制的核心要点:
- 回滚规则判定:通过
rollbackOn()方法判断异常是否触发回滚,默认只回滚RuntimeException和Error - 自定义规则优先级:
noRollbackFor>rollbackFor,子类匹配 > 父类匹配,通过异常继承树的depth计算实现 - 回滚时机:异常抛出时在
completeTransactionAfterThrowing()中立即判定处理;正常返回时在commit()中检测rollbackOnly标记 - 回滚标记机制:
setRollbackOnly()通过布尔标记实现,支持跨事务传播的标记传递 - 回滚执行流程:
processRollback()的决策树——Savepoint 回滚 → 新事务完整回滚 → 参与事务的标记传递 - 部分回滚:基于 JDBC Savepoint 实现,
Propagation.NESTED的核心底层机制