
在 Spring Boot 项目里,下面这种代码你一定写过,甚至写过很多次:
@Transactional
public void updateUser(User user) {
try {
userMapper.update(user);
otherService.doSomething();
} catch (Exception e) {
log.error("更新失败", e);
}
}
代码能跑,日志也打了,看起来“异常已经处理干净了”。
但一个问题随之而来:事务还会回滚吗?
很多人对这个问题的判断,和 Spring 实际做的事,完全不一致。

一、事务回滚的判定条件
这是一个很多人不愿意接受的事实:异常一旦被 catch 且没有重新抛出,事务默认不会回滚。
原因不复杂,但很容易被忽略。
Spring 的事务判断逻辑是:
- 方法正常返回 → 提交事务
- 方法抛出异常 → 判断是否回滚
而你在 catch 里把异常“消化”掉了,对 Spring 来说,这个方法是正常结束的。
二、Spring 事务的回滚机制
Spring 的事务,本质是靠 AOP(面向切面编程) 包起来的。
它只关心一件事:目标方法是正常结束,还是异常结束
而不是你业务逻辑里“有没有出过问题”。
所以这段代码:
catch (Exception e) {
log.error("xxx", e);
}
在事务看来,等价于:
// 什么都没发生
这也是为什么很多人会遇到:
三、异常类型与默认回滚规则
这里再打破一个常见误解。
很多人以为:
只要抛异常,事务就一定回滚
但 Spring 默认只对 RuntimeException 和 Error 回滚。
比如:
@Transactional
public void test() throws Exception {
userMapper.insert(user);
throw new Exception("error");
}
默认不会回滚。
除非你这样写:
@Transactional(rollbackFor = Exception.class)
所以事务是否回滚,其实取决于两个条件:
- 异常有没有被抛出
- 抛出的异常类型是否在回滚规则内
四、常见的回滚失效写法
下面这些写法,风险都非常高。
写法一:只记录日志
catch (Exception e) {
log.error("失败", e);
}
结果:❌ 不回滚
写法二:返回失败结果
catch (Exception e) {
return false;
}
结果:❌ 不回滚
写法三:吞掉异常继续执行
catch (Exception e) {
log.error("异常", e);
}
// 后面还有数据库操作
结果:❌ 不回滚,还可能写更多脏数据
五、触发回滚的正确方式
方式一:catch 后重新抛出运行时异常(最常用)
catch (Exception e) {
log.error("失败", e);
throw new RuntimeException(e);
}
优点:
这是最推荐的方式。
方式二:显式标记回滚(不抛异常)
catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
}
这种方式适合:
⚠️ 但要注意:这种写法非常依赖上下文,不适合滥用。
方式三:一开始就别 catch(能不 catch 就不 catch)
很多业务代码,其实根本不需要在 Service 层 catch 异常。
@Transactional
public void update() {
userMapper.update();
otherService.doSomething();
}
让异常自然抛出,由全局异常处理统一兜底,反而是最干净、最安全的做法。
六、catch 的真正适用场景是什么
一个简单但实用的判断标准:如果你 catch 了异常,是为了“改变程序流程”,那你必须明确处理事务结果。
比如:
这时你要非常清楚:
否则,catch 就是在给事务埋雷。
顺带提一个高频坑:
this.otherMethod();
如果 otherMethod() 也加了 @Transactional,事务很可能根本没生效。
因为 Spring 的事务是通过代理生效的,自调用不会走代理。
这也是很多“明明抛异常了,事务还是没回滚”的根源之一。

小结
事务是否回滚,和你“有没有 catch 异常”关系非常大。
- catch 了,不抛 → 默认不回滚
- 抛了受检异常 → 默认不回滚
- 想回滚 → 要么抛 RuntimeException,要么显式标记
事务不是“自动兜底”,它只对明确的异常信号负责。
希望这篇解析能帮你彻底理清 Spring 事务与异常处理的纠葛。对于更多深入的Java与系统架构讨论,欢迎访问云栈社区进行交流。
