SpringAOP自调用导致事务失效
若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有@Transactional 注解的方法的事务会失效。
当一个没有 @Transactional 注解的方法在同一个类中调用一个有 @Transactional 注解的方法时,@Transactional 注解的事务管理将不起作用。这是因为事务管理是通过代理对象实现的,而在同一个类中的方法调用不会通过代理对象。这种情况下,事务管理将失效,导致方法不在事务范围内执行。
为了解决这个问题,你可以采用以下两种方法:
1、将具有 @Transactional 注解的方法移动到另一个独立的 Spring bean 中,然后通过注入这个 bean 的方式在当前类中调用其方法。这样调用会经过代理对象,事务管理将正常工作。
@Service
public class TransactionalService {
@Transactional
public void methodWithTransaction() {
//...
}
}
@Service
public class NonTransactionalService {
@Autowired
private TransactionalService transactionalService;
public void methodWithoutTransaction() {
// 调用带有 @Transactional 注解的方法
transactionalService.methodWithTransaction();
}
}
2、使用 AopContext.currentProxy() 显式地获取代理对象,这样你可以在同一个类中调用具有 @Transactional 注解的方法,同时保留事务管理功能。
@Service
public class MyService implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Transactional
public void methodWithTransaction() {
//...
}
public void methodWithoutTransaction() {
// 获取代理对象
MyService proxy = (MyService) AopContext.currentProxy();
// 通过代理对象调用带有 @Transactional 注解的方法
proxy.methodWithTransaction();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
注意:为了使用 AopContext.currentProxy(),需要在 Spring 配置中启用 expose-proxy 属性:
<aop:config expose-proxy="true">
<!-- 其他配置 -->
</aop:config>
或者在 Java 配置中:
@EnableAspectJAutoProxy(exposeProxy = true)
public class AppConfig {
//...
}
产生该问题的原因
当在 Spring 应用程序中使用@Transactional
注解时,Spring 会为被注解的类创建一个代理对象。这个代理对象会根据方法上的@Transactional
注解来决定如何处理事务。
当你调用一个被@Transactional
注解修饰的方法时,代理对象会拦截这个方法调用,然后根据注解中的配置(例如传播行为、隔离级别等)来启动或加入一个事务。在事务处理完成后,代理对象会负责提交或回滚事务。
对于没有 @Transactional
注解的方法,代理对象会直接调用被代理对象的实际方法,不会涉及事务处理。这也意味着,如果你在同一个类中调用一个有 @Transactional
注解的方法,而调用者方法没有注解(也就是没有经过代理对象调用),那么 @Transactional
注解将不起作用。
注意: 当一个类中的方法使用了 @Transactional 注解,Spring 会在创建该类的实例时为其生成一个代理对象。具体而言,这个过程发生在以下时机:
1、容器启动时:当 Spring 容器启动时,它会扫描所有的 Bean 定义并创建相应的 Bean 实例。如果一个类中的方法带有 @Transactional 注解,Spring 容器会在创建这个类的实例时为其生成一个代理对象。
2、懒加载:如果你将 Bean 配置为懒加载(例如在 XML 配置中使用 lazy-init=“true” 或在 Java 配置中使用 @Lazy 注解),那么代理对象的创建将会在第一次请求这个 Bean 时进行。这意味着,在第一次调用该类的任何方法之前,Spring 容器会先创建一个代理对象。
代理对象的创建过程是透明的,这意味着从开发者的角度看,你将直接使用 Spring 容器中的 Bean,而不需要关心代理对象的创建和管理。Spring 容器会确保在需要时自动创建和使用代理对象。例如,当你通过依赖注入(如 @Autowired)获取一个带有 @Transactional 注解的 Bean 时,你实际上获取的是代理对象,而不是目标对象。
当自动注入带有@Transactional注解的方法时注入的其实是代理对象
总结
当一个类中的方法A(没有 @Transactional 注解)内部调用另一个方法B(有 @Transactional 注解),并且这两个方法都是同一个类的成员方法时,事务注解可能会失效。原因如下:
1 、当你调用方法A时,是调用的代理对象。然而,由于方法A上没有 @Transactional 注解,代理对象会直接调用目标对象的方法A。
2 、当执行到方法A里面发现要执行该类的方法B时,由于是在目标对象中进行调用的,所以又直接调用了目标对象中的方法B。而目标对象中的方法B是没有代理的,从而导致事务管理失效。