第7篇-提交、回滚与异常规则
约 2234 字大约 7 分钟
2026-05-24
四、processRollback():实际回滚事务"
// 类路径:org.springframework.transaction.support.AbstractPlatformTransactionManager
public abstract class AbstractPlatformTransactionManager
implements PlatformTransactionManager, Serializable {
/**
* 【核心方法】:实际回滚事务。
*
* 这个方法会:
* 1. 触发 beforeCompletion 回调
* 2. 如果有保存点,就回滚到保存点
* 3. 如果是新事务,调用 doRollback()
* 4. 如果是参与已有事务,就标记为 rollback-only
* 5. 触发 afterCompletion 回调
*
* @param status 事务状态
* @param unexpected 是否是"意外"回滚
*/
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
// 1. 【核心】:触发 beforeCompletion 回调
triggerBeforeCompletion(status);
// 2. 如果有保存点,就回滚到保存点
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
unexpectedRollback = status.isGlobalRollbackOnly();
status.rollbackToHeldSavepoint(); // 【核心】:回滚到保存点
}
// 3. 【核心】:如果是新事务,调用 doRollback()
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
unexpectedRollback = status.isGlobalRollbackOnly();
doRollback(status); // 【核心】:实际回滚事务
}
// 4. 如果是参与已有事务,就标记为 rollback-only
else {
// 参与已有事务(比如,PROPAGATION_REQUIRED)
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
doSetRollbackOnly(status); // 【核心】:标记为 rollback-only
}
}
catch (RuntimeException | Error ex) {
// 回滚失败
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
// 5. 【核心】:如果事务被标记为 globalRollbackOnly,抛出 UnexpectedRollbackException
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
// 6. 【核心】:清理资源
cleanupAfterCompletion(status);
}
}
}关键发现:
processRollback()会触发多个回调:beforeCompletion():完成前调用afterCompletion():完成后调用
如果有保存点,就回滚到保存点:
- 这是
PROPAGATION_NESTED的处理方式 - 回滚到保存点后,嵌套事务的修改就会被撤销
- 这是
如果是参与已有事务,就标记为
rollback-only:- 这是
PROPAGATION_REQUIRED或PROPAGATION_SUPPORTS的处理方式 - 标记为
rollback-only后,整个事务都会被回滚
- 这是
五、异常回滚规则的匹配逻辑"
Spring 事务的异常回滚规则是通过 rollbackOn() 方法匹配的。
5.1 RuleBasedTransactionAttribute.rollbackOn() 方法"
// 类路径:org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
public class RuleBasedTransactionAttribute extends DefaultTransactionAttribute implements Serializable {
@Nullable
private List<RollbackRuleAttribute> rollbackRules;
/**
* 【核心方法】:判断给定异常是否应该触发回滚。
*
* 这个方法会:
* 1. 遍历所有回滚规则
* 2. 找到"最深"的匹配规则(深度最小)
* 3. 如果找到的是 RollbackRuleAttribute,就回滚
* 4. 如果找到的是 NoRollbackRuleAttribute,就不回滚
* 5. 如果没找到规则,就调用父类的 rollbackOn()(默认:RuntimeException 和 Error 回滚)
*
* @param ex 抛出的异常
* @return 是否应该回滚
*/
@Override
public boolean rollbackOn(Throwable ex) {
RollbackRuleAttribute winner = null;
int deepest = Integer.MAX_VALUE;
if (this.rollbackRules != null) {
// 1. 遍历所有回滚规则
for (RollbackRuleAttribute rule : this.rollbackRules) {
int depth = rule.getDepth(ex); // 获取异常的深度
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule; // 找到"最深"的匹配规则
}
}
}
// 2. 如果没有规则匹配,就调用父类的方法(默认:RuntimeException 和 Error 回滚)
if (winner == null) {
return super.rollbackOn(ex); // 默认规则
}
// 3. 【核心】:如果找到的是 RollbackRuleAttribute,就回滚
// 如果找到的是 NoRollbackRuleAttribute,就不回滚
return !(winner instanceof NoRollbackRuleAttribute);
}
}5.2 默认回滚规则"
// 类路径:org.springframework.transaction.interceptor.DefaultTransactionAttribute
public class DefaultTransactionAttribute extends DefaultTransactionDefinition
implements TransactionAttribute, Serializable {
/**
* 【核心方法】:默认回滚规则。
*
* 默认规则:
* - RuntimeException:回滚
* - Error:回滚
* - 检查异常(Checked Exception):不回滚
*
* @param ex 抛出的异常
* @return 是否应该回滚
*/
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
}关键发现:
Spring 事务的默认回滚规则是:
RuntimeException:回滚Error:回滚- 检查异常(Checked Exception):不回滚
如何改变默认规则?
- 使用
@Transactional(rollbackFor = Exception.class):让所有 Exception 都触发回滚 - 使用
@Transactional(noRollbackFor = RuntimeException.class):让 RuntimeException 不触发回滚
- 使用
规则匹配是"最深"的获胜:
- 如果有多个规则匹配同一个异常,那么"深度最小"的规则获胜
- 深度:异常类到匹配规则的距离(0 = 完全匹配)
六、TransactionAspectSupport 中的异常处理"
在 TransactionAspectSupport.invokeWithinTransaction() 方法中,会调用 completeTransactionAfterThrowing() 来处理异常:
// 类路径:org.springframework.transaction.interceptor.TransactionAspectSupport
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
/**
* 【核心方法】:事务方法抛出异常后的处理。
*
* 这个方法会:
* 1. 判断异常是否应该触发回滚(调用 rollbackOn())
* 2. 如果应该回滚,调用 processRollback()
* 3. 如果不应该回滚,调用 processCommit()
*
* @param txInfo 事务信息
* @param ex 抛出的异常
*/
protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
if (txInfo.transactionAttribute != null) {
// 1. 【核心】:判断异常是否应该触发回滚
if (txInfo.transactionAttribute.rollbackOn(ex)) {
// 2. 如果应该回滚,调用 processRollback()
try {
processRollback(txInfo.transactionStatus, false);
}
catch (RuntimeException | Error e) {
logger.error("Application exception overridden by rollback exception", e);
ex = e;
}
}
// 3. 如果不应该回滚,调用 processCommit()
else {
try {
processCommit(txInfo.transactionStatus);
}
catch (RuntimeException | Error e) {
logger.error("Application exception overridden by commit exception", e);
throw e;
}
}
}
}
}关键发现:
- 如果异常应该触发回滚,就调用
processRollback() - 如果异常不应该触发回滚,就调用
processCommit()!- 这意味着:即使方法抛了异常,只要不符合回滚规则,事务还是会提交!
- 比如:方法抛了
Exception(检查异常),但@Transactional没有设置rollbackFor = Exception.class,那么事务会提交!
七、rollbackOnly 的原理"
7.1 什么是 rollbackOnly?
rollbackOnly 是一个标记,表示事务应该被回滚。
事务 A(外层事务)
|
+-- 调用事务 B(内层事务)
| |
| +-- 抛出异常
| |
| +-- 事务 B 被标记为 rollback-only
|
+-- 事务 A 提交时,发现事务被标记为 rollback-only
|
+-- 回滚事务!7.2 如何设置 rollbackOnly?
@Service
public class UserService {
@Autowired
private TransactionStatus transactionStatus;
@Transactional
public void createUser(User user) {
userMapper.insert(user);
// 【核心】:标记为 rollback-only
transactionStatus.setRollbackOnly();
}
}7.3 rollbackOnly 是如何影响提交的?
// 类路径:org.springframework.transaction.support.DefaultTransactionStatus
public class DefaultTransactionStatus extends DefaultTransactionDefinition {
private boolean rollbackOnly = false;
/**
* 标记为 rollback-only。
*/
public void setRollbackOnly() {
this.rollbackOnly = true;
}
/**
* 检查是否标记为 rollback-only。
*/
public boolean isLocalRollbackOnly() {
return this.rollbackOnly;
}
}在 commit() 方法中:
// 类路径:org.springframework.transaction.support.AbstractPlatformTransactionManager
public abstract class AbstractPlatformTransactionManager
implements PlatformTransactionManager, Serializable {
@Override
public final void commit(TransactionStatus status) throws TransactionException {
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
// 【核心】:如果事务被标记为 rollback-only,就回滚!
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus, false);
return;
}
// ... 省略后续代码 ...
}
}八、UnexpectedRollbackException 的原理"
8.1 什么是 UnexpectedRollbackException?
UnexpectedRollbackException 是一个异常,表示事务被"意外"回滚了。
什么时候会抛出这个异常?
事务 A(外层事务)
|
+-- 调用事务 B(内层事务)
| |
| +-- 抛出异常
| |
| +-- 事务 B 被标记为 globalRollbackOnly
| |
| +-- 事务 B 回滚
| |
| +-- 事务 B 的 processRollback() 抛出 UnexpectedRollbackException
| | (因为 globalRollbackOnly = true)
| |
| +-- 事务 A 捕获异常
| |
| +-- 事务 A 也想回滚
| |
| +-- 事务 A 的 processRollback() 抛出 UnexpectedRollbackException
| (因为 globalRollbackOnly = true)
|
+-- 最终:外层调用者收到 UnexpectedRollbackException8.2 如何避免 UnexpectedRollbackException?
// 类路径:org.springframework.transaction.support.AbstractPlatformTransactionManager
public abstract class AbstractPlatformTransactionManager
implements PlatformTransactionManager, Serializable {
/**
* 设置是否在全局回滚标记后,尽早失败。
*
* 如果设置为 true,那么只要事务被标记为 globalRollbackOnly,
* 就会立即抛出 UnexpectedRollbackException。
*
* 如果设置为 false(默认),那么要等到事务提交时,
* 才会抛出 UnexpectedRollbackException。
*/
public final void setFailEarlyOnGlobalRollbackOnly(boolean failEarlyOnGlobalRollbackOnly) {
this.failEarlyOnGlobalRollbackOnly = failEarlyOnGlobalRollbackOnly;
}
}九、扩展点总结"
在这篇文章中,我们提到了以下几个扩展点:
| 扩展点 | 场景 | 如何扩展 |
|---|---|---|
| 自定义回滚规则 | 你想实现自己的回滚规则 | 继承 RuleBasedTransactionAttribute,重写 rollbackOn() 方法 |
| 自定义提交/回滚逻辑 | 你想在提交/回滚前后执行一些操作 | 实现 TransactionSynchronization 接口,并注册到 TransactionSynchronizationManager |
| 改变默认回滚规则 | 你想让检查异常也触发回滚 | 使用 @Transactional(rollbackFor = Exception.class) |
| 避免 UnexpectedRollbackException | 你想在事务被标记为 rollback-only 时,尽早失败 | 设置 setFailEarlyOnGlobalRollbackOnly(true) |
十、小结与伏笔"
好了,到这里,我们已经把事务"提交与回滚"的神秘面纱揭开了。
让我们来回顾一下整个流程:
- 当调用一个事务方法时,
TransactionInterceptor.invoke()会被调用` invoke()方法调用了TransactionAspectSupport.invokeWithinTransaction()方法`invokeWithinTransaction()方法会:- 如果方法正常返回,调用
processCommit() - 如果方法抛出异常,调用
completeTransactionAfterThrowing()
- 如果方法正常返回,调用
completeTransactionAfterThrowing()方法会:- 调用
rollbackOn()判断是否应该回滚 - 如果应该回滚,调用
processRollback() - 如果不应该回滚,调用
processCommit()
- 调用
processCommit()方法会:- 触发
beforeCommit()回调 - 触发
beforeCompletion()回调 - 调用
doCommit()实际提交事务 - 触发
afterCommit()回调 - 触发
afterCompletion()回调
- 触发
processRollback()方法会:- 触发
beforeCompletion()回调 - 调用
doRollback()实际回滚事务 - 触发
afterCompletion()回调
- 触发
🔮 伏笔:Spring 事务与 MyBatis 是如何集成的?
你可能会注意到,我们已经讲完了 系列一(Spring 事务) 的所有文章。
那么问题来了:
- MyBatis 的
SqlSession是如何与 Spring 的事务集成的? - MyBatis 的一级缓存与 Spring 事务有什么关系?
- 如果 MyBatis 执行的 SQL 抛出异常,Spring 事务是如何回滚的?
这些问题,我们会在系列三(MyBatis 系列)中详细讲解。
下一系列的预告:《系列二:数据源系列》
在这个系列中,我们会讲解:
- JDBC 规范与 DriverManager 的 legacy`
- Druid 连接池的整体架构`
- 连接的借与还(getConnection 全流程)`
- Druid 过滤链与监控统计`
- MySQL 驱动的 JDBC 实现`
- 连接的生命周期与事务边界`
写在最后:这篇文章比较长,但我觉得值得。因为只有当理解了事务的"提交与回滚"机制,你才能真正理解 Spring 事务的工作原理。如果你有什么疑问,欢迎留言讨论!