更多事务失效场景:事务失效
同类调用导致的@Transactional失效
同一个类中的内部方法间的调用也是会导致@Transactional失效,当然这也不是说全部失效,具体场景具体分析。下面我们首先看一个案例,在AServiceImpl中,有两个实例方法a(),b() ,并且在testMerge()中进行调用。
public interface AService {
public void a();
public void b();
}
@Service()
public class AServiceImpl implements AService{
public void a() {
standardBakService.save(testAService.buildEntity());
}
public void b() {
standardBak2Service.save(testBService.buildEntity2());
throw new RuntimeException("b error");
}
}
@Transactional
public String testMerge() {
a();
b();
return "ok";
}
这里我们看下它的流程是什么:
- 事务在 testMerge() 方法执行时开启
- 生成一个代理对象进行调用a(),b()
- 进入a()中,此时代理对象失效,换用为this进行调用
- 进入b()中,同理使用this调用,但抛出异常
- Spring 事务代理失效了(a(),b()方法均使用this执行),但由于事务传播性,@Transactional 仍然会生效
所以 b()中抛出 RuntimeException,事务会根据默认规则(遇到运行时异常时回滚)进行回滚。
注意这里并不是因为代理对象生效,而是 Spring 会认为 a() 和 b() 依然是在事务的上下文中执行(由事务的传播性),所以testMerge()是认为事务生效的,但不建议这样写的。
但如果是在方法中通过this调用同类的一个事务方法,可能就会出现问题了:
ublic String testMerge() {
a();
b();
return "ok";
}
public void a() {
standardBakService.save(testAService.buildEntity());
}
@Transactional
public void b() {
standardBak2Service.save(testBService.buildEntity2());
throw new RuntimeException("b error");
}
原因就是调用b(),我们还是通过this进行调用的,走到b()方法中,压根就不会开启事务,所以是失效的
同理像下面这种也是,可以先猜下这个案例中有几个事务生效呢?
public interface AService {
public void a();
public void b();
}
@Service()
public class AServiceImpl implements AService{
@Transactional(propagation = Propagation.REQUIRED)
public void a() {
this.b();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void b() {
}
}
我们在a()中通过this调用b()方法,不是通过代理来调用的,this指向目标对象,因此调用this.b()将不会执行b事务切面,但是a()却是通过代理模式来做的,所以只对a方法进行了事务增强,没有对b方法进行增强
b中的事务会不会生效?
不会,a的事务会生效,b中不会有事务,因为a中调用b属于内部调用,没有通过代理,所以不会有事务产生。
如果想要b中有事务有效,要如何做?
此处a调用b方法时,通过AOP代理换用this即可执行事务切面,对应事务生效。
具体实现:
- 引入spring动态代理
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
- 方法调用改成动态代理调用
- 注入代理对象
- 使用 ApplicationContext 获取代理对象
注入代理对象:
@Service
public class AServiceImpl implements AService {
@Autowired
private AService aService; // 注入 AService 的代理对象
@Transactional(propagation = Propagation.REQUIRED)
public void a() {
// 使用代理对象调用 b() 方法
this.aService.b();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void b() {
// b 的事务将是一个新的事务
}
}
使用 ApplicationContext 获取代理对象
@Service
public class AServiceImpl implements AService {
@Autowired
private ApplicationContext context; // 获取 Spring 上下文
@Transactional(propagation = Propagation.REQUIRED)
public void a() {
// 通过 Spring 上下文获取代理对象
AService aService = context.getBean(AService.class);
aService.b(); // 通过代理对象调用 b()
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void b() {
// b 的事务将是一个新的事务
}
}
使用AopContext.currentProxy() ,也可以在当前方法执行上下文中获取代理对象
AopContext.currentProxy() 能够获取当前方法的代理对象,这意味着即使 a() 方法是通过 this 调用的,b() 方法依然能够通过代理对象来执行
@Service
public class AServiceImpl implements AService {
@Transactional(propagation = Propagation.REQUIRED)
public void a() {
// 使用代理调用 b() 方法
AopContext.currentProxy(); // 获取当前的代理对象
this.b(); // b() 方法将会通过代理执行,事务传播将生效
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void b() {
// 这个方法会开启一个新的事务
System.out.println("In method b, a new transaction is started.");
}
}
可能报错:
Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
在 @SpringBootApplication 所在的主类 或者AopConfig配置类中添加配置:
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
为什么需要 exposeProxy?
Spring AOP 默认使用 JDK 动态代理 或 CGLIB 代理 来处理 AOP 相关的功能。这些代理本质上是对目标对象的包装,但是如果你需要在目标对象中访问代理对象(如 AopContext.currentProxy()),你需要明确告诉 Spring 曝露(Expose)代理对象。
- exposeProxy = true:表示 Spring 会将当前的代理对象暴露出来,从而允许在当前方法中通过 AopContext.currentProxy() 获取代理对象。
- proxyTargetClass = true:表示强制使用 CGLIB 代理,即使目标对象实现了接口。如果你的目标对象是通过接口进行代理的(默认情况),可以使用这个配置来强制使用基于类的代理(CGLIB)。
强烈推荐看这篇文章:同类方法调用 @Transactional 失效与解决_transactional 同类调用-CSDN博客