Spring事务失效的本质是:Spring的声明式事务管理是基于AOP(动态代理)实现的,任何破坏代理机制或事务上下文的行为都会导致失效。
一、核心原理回顾
Spring通过AOP代理为加了@Transactional注解的Bean注入事务管理逻辑。当你调用userService.update()时,你实际上调用的是Spring生成的代理对象的方法,而不是原始对象的方法。代理方法的结构大致如下:
// Spring 事务代理伪代码
public class UserServiceProxy extends UserService {
private UserService target; // 原始对象
public void update(User user) {
TransactionStatus status = null;
try {
// 1. 获取事务管理器
// 2. 开启事务(status = transactionManager.getTransaction())
// 3. 调用原始对象的方法 target.update(user);
// 4. 提交事务(transactionManager.commit(status))
} catch (Exception e) {
// 5. 判断异常是否需要回滚
if (needRollback(e)) {
transactionManager.rollback(status); // 回滚
}
throw e;
}
}
}
任何导致无法执行上述代理逻辑的情况,都会造成事务失效。
二、事务失效的八大场景及解决方案
1. 方法非Public修饰(最常见易错)
原因:Spring的CGLIB代理基于继承,无法代理private、protected或包级访问的方法。JDK动态代理也要求实现接口的必须是public方法。
现象:事务注解不生效,方法以非事务方式运行。
解决:确保被@Transactional注解的方法是public的。
2. 同类方法自调用(最隐蔽的坑)
- 原因:如下所示,自调用 this.updateUserStatus() ,this 直接调用了原始对象的方法,当你从Spring容器中@Autowired这个Bean时,你拿到的是代理对象,而不是原始的目标对象。关键在于 this 指向的是原始对象而非代理对象,因此无法进入代理对象的事务,因此事务注解不会生。
- 代码事例:
@Service
public class UserService {
public void updateUser(User user) {
// ... 一些逻辑
this.updateUserStatus(user.getId()); // 自调用,事务失效!
}
@Transactional
public void updateUserStatus(Long userId) {
// 此方法的事务不会生效
userDao.updateStatus(userId, 1);
}
}
- 解决方案:
- 方案A(推荐):将updateUserStatus方法抽到另一个Service中(如UserStatusService),然后通过注入UserStatusService来调用。
@Service
public class UserService {
// 注入另一个Service(实际上是它的代理对象)
@Autowired
private UserStatusService userStatusService;
public void createUser(User user) {
// ... 一些其他业务逻辑
userStatusService.updateUserStatus(user.getId(), "ACTIVE"); // 通过代理对象调用
}
}
@Service // 将事务方法拆分到另一个Service
public class UserStatusService {
@Autowired
private UserRepository userRepository;
@Transactional // 现在这个注解会正常工作了
public void updateUserStatus(Long userId, String status) {
userRepository.updateStatus(userId, status);
}
}```
- 方案B:通过AopContext获取当前代理对象再调用(需开启exposeProxy)。
```java
@EnableAspectJAutoProxy(exposeProxy = true) // 启动类上加注解
((UserService) AopContext.currentProxy()).updateUserStatus(user.getId());
3. 异常类型不正确或被捕获
原因: Spring默认只在抛出运行时异常(RuntimeException) 和 Error 时回滚事务。如果抛出的是受检异常(如Exception),或者异常被方法自己try-catch吞掉了,事务不会回滚。
@Transactional
public void createOrder(Order order) {
try {
orderDao.save(order);
// 模拟业务逻辑
int i = 1 / 0; // 会抛出RuntimeException
} catch (Exception e) {
// 异常被捕获并处理了,代理感知不到异常,会提交事务!
log.error("创建订单失败", e);
}
}
解决方案:
- 在@Transactional注解中指定rollbackFor属性:
@Transactional(rollbackFor = Exception.class) // 所有异常都回滚
- 如果业务需要捕获异常,必须在catch块中手动抛出运行时异常:
catch (Exception e) {
log.error("创建订单失败", e);
throw new RuntimeException(e); // 或者自定义运行时异常
}
4. 数据库引擎不支持事务
- 原因:如果你使用的是MySQL数据库,并且表使用的存储引擎是MyISAM,那么事务会失效,因为MyISAM根本不支持事务。
- 解决:将表的存储引擎改为InnoDB。
5. Propagation 传播行为设置不当
- 原因:传播行为配置错误。例如:
@Service
public class UserService {
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// ...
userService.methodB(); // 如果methodB的传播行为是NOT_SUPPORTED或NEVER,则会挂起或抛出异常
}
}
@Service
public class OtherService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodB() {
// 此方法将以非事务方式执行
}
}
- 解决:根据业务逻辑,正确配置传播行为。务必理解REQUIRED, REQUIRES_NEW, NESTED, NOT_SUPPORTED等行为的区别。
6. 未被Spring容器管理
-
原因:如果你自己new了一个对象(new UserService()),然后调用其上的@Transactional方法,事务绝对不生效。因为Spring无法为这个你自己new出来的对象提供代理。
-
解决:确保对象是通过Spring容器注入的(使用@Autowired)。
7. 多数据源下未指定事务管理器
-
原因:项目配置了多个数据源和事务管理器,但@Transactional注解在使用时,不知道应该使用哪个事务管理器。
-
解决:在@Transactional注解上显式指定使用哪个事务管理器:
@Transactional(transactionManager = "orderTransactionManager") // 指定事务管理器Bean名称
public void createOrder() {
// ...
}
8. 异步方法(@Async)
原因:@Async和@Transactional都是基于AOP代理的。在一个异步方法中,事务的开启和提交是在原始线程,而实际业务逻辑的执行是在另一个线程,两者不在同一个线程上下文中,因此事务会失效。
解决:避免在异步方法中处理事务性操作。如果必须,可以考虑在异步方法内部手动编程式事务。
三、排查工具与技巧
- 开启调试日志:在application.yml中开启相关日志,查看事务的开启、提交、回滚情况。
logging:
level:
org.springframework.jdbc.datasource.DataSourceTransactionManager: DEBUG
org.springframework.orm.jpa.JpaTransactionManager: DEBUG
- 检查代理是否生效:在IDE中Debug,查看注入的Service对象是否是类似UserServiceEnhancerBySpringCGLIBEnhancerBySpringCGLIBEnhancerBySpringCGLIB…这样的代理对象。
总结
Spring事务失效的根本原因可以归结为三点:
-
代理失效:非public方法、自调用、未由Spring管理。
-
异常处理不当:异常类型不对或异常被捕获。
-
上下文环境错误:数据库不支持、传播行为配置错误、多数据源冲突、线程上下文切换。
4341

被折叠的 条评论
为什么被折叠?



