以下是 4 种最常见的"事务失效"场景,请务必避开:
场景一:由于"同类内部调用"导致的失效(最坑!)
这是新手最容易犯的错。
现象 :你有一个类 UserService,里面有两个方法 A 和 B。
-
方法 A:没有 加
@Transactional。 -
方法 B:加了
@Transactional(要求回滚)。 -
A 调用了 B。
代码示例(失效版):
java
@Service
public class UserService {
// 方法 A:没有事务
public void methodA() {
// ... 做一些事
this.methodB(); // <--- 关键在这里!直接调用内部方法
}
// 方法 B:声明了事务
@Transactional
public void methodB() {
userMapper.insert(new User());
int i = 1 / 0; // 模拟报错,期望回滚
}
}
结果 :当你调用 methodA 时,methodB 的事务不会生效,数据插入成功,不会回滚。
原因分析:
Spring 的事务是基于 代理模式 (Proxy Pattern) 实现的。
-
当你从外部 (比如 Controller)调用
UserService时,你拿到的其实是 Spring 生成的代理对象。 -
代理对象拿到请求,先开启事务,然后调用真正的目标对象。
-
但是,如果你在
methodA内部直接写this.methodB(),这里的this指的是目标对象本身,而不是代理对象。 -
这就相当于绕过了代理,直接执行了代码。既然没经过代理,事务切面自然就没机会执行。
怎么解决?
-
方案一(推荐):拆分文件 。把
methodB移到另一个 Service 类中,然后注入进来调用。 -
方案二(自己注入自己):
java@Service public class UserService { @Autowired private UserService self; // 注入代理后的自己 public void methodA() { self.methodB(); // 通过代理对象调用,事务生效 } }
场景二:异常被"吃掉"了
这也是我们刚才讨论 AOP 顺序时提到的问题。
现象 :你自己写了 try-catch,把异常捕获了,而且没抛出去。
代码示例(失效版):
java
@Transactional
public void updateUser() {
try {
userMapper.update(new User());
int i = 1 / 0; // 报错
} catch (Exception e) {
e.printStackTrace();
// 坑点:这里没有 throw e; 事务管理器以为一切正常
}
}
原因 :Spring 事务管理器只有捕获到异常时,才会触发回滚。你自己把异常处理掉了,Spring 就会认为"操作成功",于是提交事务。
解决:
-
在
catch块里手动 抛出:throw e; -
或者手动触发生效:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
场景三:异常类型不对(默认只认 RuntimeException)
现象:代码报错了,也抛出了异常,但数据库还是没回滚。
代码示例(失效版):
java
// 假设这是一个 Checked Exception (编译时异常),比如 IO 异常
@Transactional
public void readFile() throws IOException {
userMapper.insert(new User());
// 模拟文件读取失败
throw new IOException("文件读取失败");
}
原因:
Spring 的 @Transactional 默认配置是:只有遇到 RuntimeException (运行时异常) 或 Error 时才回滚。
如果你抛出的是 Checked Exception(如 IOException, SQLException, 或者你自己定义的非运行时异常),Spring 默认是不回滚的。
解决(最稳妥的写法):
永远加上 rollbackFor 属性:
java
@Transactional(rollbackFor = Exception.class) // 只要是异常,统统回滚
public void readFile() throws IOException { ... }
场景四:方法权限不是 public
现象 :你把 @Transactional 加在了一个 private 或 protected 方法上。
原因:
Spring AOP 的底层实现(动态代理)通常要求代理的方法必须是 public 的。虽然较新的 Spring 版本(使用 CGLIB)可能在某些情况下支持,但官方建议和大多数情况下的铁律是:事务方法必须是 public。如果不是 public,Spring 会直接忽略这个注解,不报错,但也不生效。
总结一张表
| 失效场景 | 根本原因 | 解决方案 |
|---|---|---|
| 同类内部调用 | 绕过了 Spring 代理对象,使用了 this |
注入自己 (self.method()) 或 拆分 Service |
| 自己 try-catch | 异常未抛出,AOP 捕获不到 | throw e 或 手动设置 setRollbackOnly() |
| 异常类型不对 | 默认只回滚 RuntimeException |
使用 @Transactional(rollbackFor = Exception.class) |
| 非 public 方法 | AOP 代理限制 | 确保方法是 public |