Spring 事务失效场景详解
@Transactional 看起来简单,但事务失效是项目里最常见的坑之一。要判断事务为什么没回滚,先抓住一个核心:Spring 声明式事务依赖代理和异常传播。代理没生效,事务不会进;异常没抛出去,事务不知道要回滚。

场景一:异常被自己捕获了
事务通知要感知到目标方法抛出的异常,才能触发回滚。如果业务代码自己把异常吃掉了,外层事务代理就会认为方法正常结束,于是提交事务。
@Transactional
public void transfer(Integer from, Integer to, BigDecimal money) {
try {
accountMapper.decrease(from, money);
int i = 1 / 0;
accountMapper.increase(to, money);
} catch (Exception e) {
e.printStackTrace();
}
}
这段代码的问题是 catch 之后没有继续抛异常。事务代理看不到异常,就不会回滚。
正确处理方式:
@Transactional
public void transfer(Integer from, Integer to, BigDecimal money) {
try {
accountMapper.decrease(from, money);
int i = 1 / 0;
accountMapper.increase(to, money);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
或者直接不捕获,让异常自然抛出。
场景二:抛出了检查异常
Spring 默认只对 RuntimeException 和 Error 回滚。像 IOException、FileNotFoundException 这类检查异常,默认不会触发回滚。
@Transactional
public void importUser() throws FileNotFoundException {
userMapper.insert(user);
new FileInputStream("not-exist.txt");
}
如果希望检查异常也回滚,需要配置 rollbackFor。
@Transactional(rollbackFor = Exception.class)
public void importUser() throws FileNotFoundException {
userMapper.insert(user);
new FileInputStream("not-exist.txt");
}
项目里建议直接写成:
@Transactional(rollbackFor = Exception.class)
这样比依赖默认规则更清楚。
场景三:方法不是 public
Spring 为方法创建事务代理时,通常要求被增强的方法是 public。如果事务方法是默认访问级别、protected 或 private,事务可能不会按预期生效。
@Transactional(rollbackFor = Exception.class)
void updateAccount() {
accountMapper.update(account);
}
应该改成:
@Transactional(rollbackFor = Exception.class)
public void updateAccount() {
accountMapper.update(account);
}
这类问题非常隐蔽,因为代码能运行,但事务边界没有真正按你想的方式进入代理逻辑。
场景四:同类内部方法调用
这属于面试和项目里都很常见的扩展坑。Spring 事务通过代理对象生效,如果一个类内部直接用 this 调另一个带事务的方法,就绕过了代理。
@Service
public class OrderService {
public void createOrder() {
this.saveOrder();
}
@Transactional(rollbackFor = Exception.class)
public void saveOrder() {
orderMapper.insert(order);
}
}
createOrder() 内部调用 saveOrder(),没有经过 Spring 代理对象,事务可能不生效。
常见处理方式:
- 把事务方法拆到另一个 Service 中,通过 Spring 注入后调用。
- 让外层入口方法加事务。
- 必要时通过代理对象调用,但这通常不如拆分职责清楚。
场景五:数据库本身不支持事务
如果底层存储引擎不支持事务,Spring 再怎么加事务也没用。比如 MySQL 的 InnoDB 支持事务,MyISAM 不支持事务。
这类问题不一定是代码问题,而是表结构或数据库引擎选择问题。
快速排查清单
| 排查点 | 怎么判断 |
|---|---|
| 注解是否加在 public 方法上 | 检查方法访问修饰符 |
| 异常是否被吞掉 | 看 catch 后有没有重新抛出 |
| 异常类型是否会回滚 | 检查是否配置 rollbackFor |
| 是否同类内部调用 | 看调用链有没有经过 Spring 代理 |
| 数据库是否支持事务 | 检查存储引擎和连接配置 |
| 方法是否由 Spring 容器管理 | 检查对象是不是自己 new 出来的 |
面试回答模板
可以这样回答:
Spring 事务失效常见原因有几个。第一,异常被业务代码捕获后没有继续抛出,事务代理感知不到异常,所以不会回滚。第二,抛出的是检查异常,Spring 默认只回滚运行时异常,需要配置
rollbackFor = Exception.class。第三,事务方法不是 public,代理增强可能不生效。第四,同一个类内部方法调用会绕过代理对象。还有一些场景比如数据库引擎不支持事务,或者对象不是由 Spring 容器管理,也会导致事务不生效。
小结
排查事务失效,不要只盯着 @Transactional 有没有写。
真正要看两条线:
- 调用有没有经过 Spring 代理。
- 异常有没有按回滚规则抛到事务代理。
只要这两条线断了,事务就可能失效。
更多推荐




所有评论(0)