Spring事务回滚规则深度解析

以下代码来自org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn

java 复制代码
	/**
	 * Winning rule is the shallowest rule (that is, the closest in the
	 * inheritance hierarchy to the exception). If no rule applies (-1),
	 * return false.
	 * @see TransactionAttribute#rollbackOn(java.lang.Throwable)
	 */
	@Override
	public boolean rollbackOn(Throwable ex) {
		if (logger.isTraceEnabled()) {
			logger.trace("Applying rules to determine whether transaction should rollback on " + ex);
		}

		RollbackRuleAttribute winner = null;
		int deepest = Integer.MAX_VALUE;

		if (this.rollbackRules != null) {
			for (RollbackRuleAttribute rule : this.rollbackRules) {
				int depth = rule.getDepth(ex);
				if (depth >= 0 && depth < deepest) {
					deepest = depth;
					winner = rule;
				}
			}
		}

		if (logger.isTraceEnabled()) {
			logger.trace("Winning rollback rule is: " + winner);
		}

		// User superclass behavior (rollback on unchecked) if no rule matches.
		if (winner == null) {
			logger.trace("No relevant rollback rule found: applying default rules");
			return super.rollbackOn(ex);
		}

		return !(winner instanceof NoRollbackRuleAttribute);
	}

这段代码是 Spring 事务框架中 RollbackRuleAttribute 类的 rollbackOn(Throwable ex) 方法,它的作用是:判断当抛出某个异常时,是否应该触发事务回滚

我们来逐行深入解析其逻辑、设计思想和关键点。


🎯 一、核心目标

✅ 这个方法是做什么的?

给定一个异常(Throwable ex),检查当前配置的"回滚规则"(rollbackRules),决定:

这个异常发生时,事务是否应该回滚?

返回值:

  • true → 回滚
  • false → 不回滚

📚 二、背景知识:Spring 的回滚规则

@Transactional 注解中,你可以通过以下属性定义回滚行为:

java 复制代码
@Transactional(
    rollbackFor = {SQLException.class},     // 遇到这些异常就回滚
    noRollbackFor = {IOException.class}     // 遇到这些异常不回滚
)

Spring 内部会把这些配置转换成两种规则对象:

规则类型 对应类 行为
rollbackFor RollbackRuleAttribute 匹配则回滚
noRollbackFor NoRollbackRuleAttribute 匹配则不回滚

这些规则都存放在 this.rollbackRules 列表中。


🔍 三、逐行解析

1. 日志输出(调试用)

java 复制代码
if (logger.isTraceEnabled()) {
    logger.trace("Applying rules to determine whether transaction should rollback on " + ex);
}

👉 记录正在判断哪个异常是否要回滚。


2. 初始化变量

java 复制代码
RollbackRuleAttribute winner = null;
int deepest = Integer.MAX_VALUE;
  • winner:最终胜出的规则(即最匹配的那个)
  • deepest:记录当前找到的"最小继承深度",初始设为最大值,表示还没找到

📌 注意:这里"deepest"其实是"离异常最近的继承距离",名字有点反直觉。


3. 遍历所有回滚规则

java 复制代码
if (this.rollbackRules != null) {
    for (RollbackRuleAttribute rule : this.rollbackRules) {
        int depth = rule.getDepth(ex);
        if (depth >= 0 && depth < deepest) {
            deepest = depth;
            winner = rule;
        }
    }
}
关键:rule.getDepth(ex)
  • 计算该规则对应的异常类型(如 SQLException)与传入异常 ex 之间的继承层级距离
  • 例如:
    • exSQLSyntaxErrorException
    • ruleSQLException.class
    • 继承链:SQLSyntaxErrorException → SQLException → Exception
    • getDepth() 返回 1(差一级)
赢家规则(Winning Rule)判定条件:

"最浅层的规则获胜 " ------ 即 继承距离最小、最具体的异常类型优先

✅ 例子:

java 复制代码
rollbackFor = {
    Exception.class,      // depth = 2(较远)
    SQLException.class    // depth = 1(更近)
}

如果抛出 SQLTimeoutException,则 SQLException.class 规则胜出,因为它更"具体"。


4. 输出胜出规则(日志)

java 复制代码
if (logger.isTraceEnabled()) {
    logger.trace("Winning rollback rule is: " + winner);
}

👉 帮助开发者调试:到底是哪个规则起作用了。


5. 如果没有匹配的规则

java 复制代码
if (winner == null) {
    logger.trace("No relevant rollback rule found: applying default rules");
    return super.rollbackOn(ex);
}
  • 没有规则匹配当前异常(比如你只配置了 rollbackFor=SQLException,但抛出了 IOException)。
  • 此时走父类默认逻辑:super.rollbackOn(ex)
默认规则是什么?

来自 DefaultTransactionAttributeRuleBasedTransactionAttribute 的父类:

仅对 RuntimeExceptionError 及其子类自动回滚

Exception(检查异常)不自动回滚

📌 所以:

  • throw new RuntimeException() → 回滚
  • throw new IOException() → 不回滚(除非显式配置 rollbackFor

6. 判断胜出规则是否是"不回滚"规则

java 复制代码
return !(winner instanceof NoRollbackRuleAttribute);

这是最关键的一步!

胜出规则类型 返回值 是否回滚
RollbackRuleAttribute(如 rollbackFor=SQLException true ✅ 回滚
NoRollbackRuleAttribute(如 noRollbackFor=IOException false ❌ 不回滚

📌 也就是说:

  • 如果最匹配的规则是 noRollbackFor,那就不回滚
  • 否则(是 rollbackFor),就回滚

🎯 四、完整流程图解

复制代码
开始
  ↓
遍历所有 rollbackRules
  ↓
对每个 rule,计算 rule.getDepth(ex)
  ↓
保留 depth 最小(最具体)的那个 rule 作为 winner
  ↓
是否有 winner?
  ├─ 否 → 使用默认规则:RuntimeException/Error 才回滚
  └─ 是 → 看 winner 是否是 NoRollbackRuleAttribute
           ├─ 是 → 不回滚(return false)
           └─ 否 → 回滚(return true)

✅ 五、举个实际例子

java 复制代码
@Transactional(
    rollbackFor = {Exception.class},
    noRollbackFor = {IllegalArgumentException.class}
)
public void doSomething() {
    throw new IllegalArgumentException("参数错误");
}

调用 rollbackOn(ex) 时:

  1. rollbackRules 包含两个规则:
    • RollbackRuleAttribute: Exception.class
    • NoRollbackRuleAttribute: IllegalArgumentException.class
  2. getDepth(IllegalArgumentException)
    • Exception.class → depth = 1
    • IllegalArgumentException.class → depth = 0(完全匹配!)
  3. winner = NoRollbackRuleAttribute(depth=0 更小)
  4. return !(winner instanceof NoRollbackRuleAttribute)false
  5. 结果:不回滚

✅ 符合预期:noRollbackFor 优先级更高。


✅ 六、设计精妙之处

特性 说明
最具体优先 避免宽泛规则覆盖精确规则
noRollbackFor 可覆盖 rollbackFor 提供灵活控制
默认回滚策略兜底 保证未配置时也有合理行为
可扩展 可自定义 RollbackRuleAttribute

✅ 七、常见问题排查

现象 可能原因
@Transactional 不回滚 抛的是 Exception,没加 rollbackFor
明明写了 noRollbackFor 还是回滚了 异常类型不匹配,或 winner 是其他规则
自定义异常不回滚 必须继承 RuntimeException 或显式配置 rollbackFor

✅ 总结

这个 rollbackOn 方法是 Spring 事务"回滚决策引擎",它:

  1. 从多个规则中选出最匹配的一个(基于继承深度)
  2. 优先尊重 noRollbackFor(即"不回滚"规则胜出就不回滚)
  3. 没有匹配规则时,使用默认策略 (只对 RuntimeException/Error 回滚)

理解它有助于你:

  • 正确配置 @Transactional(rollbackFor/noRollbackFor)
  • 排查"事务该回滚却没回滚"或"不该回滚却回滚了"的问题
  • 深入掌握 Spring 事务的底层机制

🧠 一句话总结

"谁最像这个异常,谁说了算;如果是 noRollbackFor 赢了,就不回滚。"

相关推荐
命运之光7 小时前
杀死后台运行的jar程序,并重新运行jar包
java·pycharm·jar
那我掉的头发算什么7 小时前
【数据库】增删改查 高阶(超级详细)保姆级教学
java·数据库·数据仓库·sql·mysql·性能优化·数据库架构
申阳7 小时前
Day 1:耗时2小时,梳理并分享我作为全栈开发最依赖的“兵器库”
前端·后端·程序员
qq. 28040339847 小时前
nestjs引篇
后端·node.js·nestjs
在坚持一下我可没意见7 小时前
Java 网络编程:TCP 与 UDP 的「通信江湖」(基于TCP回显服务器)
java·服务器·开发语言·笔记·tcp/ip·udp·java-ee
哪吒编程8 小时前
保友 Pofit 2代人体工学椅,新一代智能坐享体验,从此开启
后端
cr7xin8 小时前
缓存查询逻辑及问题解决
数据库·redis·后端·缓存·go
大卫小东(Sheldon)8 小时前
使用JMH对远程接口进行压测
java