AI摘要
(一) 理解基石:Spring AOP 代理机制
Spring的声明式事务(@Transactional)是基于AOP实现的。AOP的核心是代理模式。Spring在运行时为你需要增强的Bean创建一个代理对象(Proxy)。当你调用@Transactional方法时,实际上是代理对象在替你管理事务(开启、提交、回滚)。
有两种代理:
- JDK动态代理: 针对实现了接口的类。代理对象和真实对象是兄弟关系。
- CGLIB代理: 针对未实现接口的类。代理对象是真实对象的子类。
理解了这个,下面的场景就很好懂了。
场景1:方法内部调用(最常见)
@Service
public class OrderService {
public void createOrder(Order order) {
// ... 业务逻辑
this.updateStatus(order); // 事务失效!
}
@Transactional
public void updateStatus(Order order) {
orderDao.updateStatus(order);
}
}原因: createOrder方法里的this是目标对象本身,不是被Spring注入的代理对象。调用this.updateStatus()绕过了代理,事务注解自然无效。
解决: 最佳实践是使用依赖注入,将事务方法拆分到另一个Bean中。
@Service
public class OrderService {
@Autowired
private TransactionalService transactionalService;
public void createOrder(Order order) {
// ...
transactionalService.updateStatus(order); // 通过代理对象调用,事务生效
}
}
@Service
public class TransactionalService {
@Transactional
public void updateStatus(Order order) { ... }
}场景2:异常被捕获或非RuntimeException
@Transactional默认回滚的异常是RuntimeException和Error。
@Transactional
public void updateOrder() {
try {
orderDao.update(...);
int i = 1 / 0; // 抛出ArithmeticException (RuntimeException)
} catch (Exception e) {
// 捕获了异常,事务不会回滚!
logger.error("error", e);
}
}
@Transactional
public void updateOrder() throws FileNotFoundException {
orderDao.update(...);
throw new FileNotFoundException(); // 检查异常,默认不回滚!
}解决:
// 方案1:在catch中抛出运行时异常
catch (Exception e) {
throw new RuntimeException("业务失败", e);
}
// 方案2:修改注解,指定回滚异常类型
@Transactional(rollbackFor = Exception.class)场景3:方法访问权限非public
因为Spring AOP代理(特别是CGLIB)无法代理非public方法。
@Transactional
private void updateSecretData() { ... } // 事务无效!场景4:数据库引擎不支持事务
如MySQL的MyISAM引擎就不支持事务,必须使用InnoDB。
场景5:自身Bean的调用(进阶版)
即使在同一个Service里,通过@Autowired注入自己来调用,也会因为循环依赖和代理时机问题可能导致事务在某些情况下失效,虽然比内部调用好,但也不推荐。
@Service
public class UserService {
@Autowired
private UserService self; // 注入自己
public void methodA() {
self.methodB(); // 通过代理调用
}
@Transactional
public void methodB() { ... }
}场景6:事务传播行为设置错误
例如,如果方法上设置了@Transactional(propagation = Propagation.NOT_SUPPORTED),则表示以非事务方式运行,挂起当前事务。
总结: 遇到事务不生效,就按这个清单逐一排查:代理 -> 异常 -> 权限 -> 数据库 -> 传播行为。