一、使用final、static修饰的事务会失效
如果Spring 使用了 Cglib 代理实现 (比如你的代理类没有实现接口),而你的业务方法敲好使用了 final 或者 static 关键字,那么事务也会失败。更具体的说,它应该抛出异常,因为 Cglib 使用字节码增强技术生成被代理类的子类并重写代理类的方法来实现代理。如果被代理的方法使用 final 或 static 关键字,则子类不能重写被代理的方法。
如果 Spring 使用 JDK 动态代理实现,JDK动态代理是基于接口实现的,那么 final 和 static 修饰的方法也就无法被代理。
二、同一类中方法相互调用
Spring中的事务是通过AOP来实现,对需要spring管理事务的bean生成了代理对象,然后通过代理对象拦截了目标方法的执行,在方法前后添加了事务的功能,所以必须通过代理对象调用目标方法的时候,事务才会起效。在一个类里面的相互调用是没有使用代理的,所以会失效。
@Service
public class UserService {
public void createUser() {
// 自调用,事务失效!
this.insertUser();
}
@Transactional
public void insertUser() {
// 数据库操作
}
}
当类内部方法 A 调用本类的事务方法 B 时,调用的是 this.B(),而非代理对象的方法,导致事务拦截失效。
解决办法:
-
方法一: 将 insertUser() 方法抽到另一个 Service 中。
-
方法二: 通过 AopContext 获取当前代理对象(需开启 @EnableAspectJAutoProxy(exposeProxy = true)):
((UserService) AopContext.currentProxy()).insertUser();
三、数据库不支持事务
在mysql5之前,默认的数据库引擎是 myisam不支持事务,现在的innodb支持。
四、自己捕获异常
@Transactional
public void update() {
try {
// 数据库操作(可能抛出异常)
} catch (Exception e) {
// 仅记录日志,未抛出 → 事务提交!
log.error("Error", e);
}
}
在方法内捕获异常并未重新抛出,事务拦截器无法感知异常,不会触发回滚。
五、异常类型未正确配置
@Transactional 默认仅对 RuntimeException 和 Error 回滚。若方法抛出受检异常(如 Exception)或自定义非运行时异常,事务不会回滚。
// 明确指定回滚的异常类型
@Transactional(rollbackFor = {Exception.class, MyCustomException.class})
public void updateData() throws Exception {
// ...
}
六、非public修饰
Spring AOP 基于代理(JDK 动态代理或 CGLIB)实现事务。默认情况下,代理无法拦截 private、protected 或包级私有方法。
七、事务传播行为设置不当
场景: 方法 A(无事务)调用方法 B(PROPAGATION_REQUIRED)。
原因: 若方法 A 中出现异常,方法 B 的事务不会回滚,因为 B 是在独立事务中执行。
八、多线程环境下调用
新线程中的操作不在原始事务的 Connection 上下文中,事务无法跨线程传播。
@Transactional
public void asyncUpdate() {
new Thread(() -> {
userDao.update(); // 此操作不在事务中!
}).start();
}