事务失效问题
Spring 事务失效的原因主要源于 Spring 事务管理机制的实现方式,特别是其使用的 AOP(面向切面编程) 代理模型。Spring 事务的失效问题常常是由于对事务的理解不到位或者误用
方法内的自身调用
@Override
public void save() {
save2();
}
@Transactional
public void save2() {
Person person = new Person();
person.setName("name");
save(person);
int i = 1 / 0;
}
此时IDEA也会给出代码提示:@Transactional 自调用(实际上是目标对象内的方法调用目标对象的另一个方法)在运行时不会导致实际的事务
原因很简单:Spring 事务是基于 AOP 的,只有使用代理对象调用方法时,Spring 事务才会生效。在方法内直接使用this
对象调用,相当于使用原始对象,而不是代理对象,导致事务失效。
下面我们通过调试来说明这个问题:
不使用代理,可以看到就是this来调用的
使用代理,可以看到这是一个基于JDK动态代理的对象:
解决办法:可以通过在 Service 中注入自身,然后使用注入的对象来调用方法,或者使用AopContext.currentProxy()
获取当前代理对象来调用方法。同时,需要在配置文件中引入相关依赖,并在主启动类上添加注解,使代理对象暴露。
第一种:
@Autowired
@Lazy
private PersonService personService;
@Override
public void save() {
personService.save2();
}
使用AopContext
引入依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
启动类开启:
@EnableAspectJAutoProxy(exposeProxy = true)
public class TotoroWebApplication
使用:
@Override
public void save() {
PersonService personService = (PersonService) AopContext.currentProxy();
personService.save2();
}
@Transactional
@Override
public void save2() {
Person person = new Person();
person.setName("name");
save(person);
int i = 1 / 0;
}
方法是private的
问题描述:将方法的修饰符从public
改为private
后,会有提示该方法的修饰符不能为private
,否则事务会失效。
原因分析:Spring 事务基于 CGlib 进行 AOP,CGlib 代理是基于父子类实现的,子类是代理类,父类是被代理类。如果方法是private
修饰的,子类无法重写该方法,也就无法增强事务逻辑,导致事务失效。同理,方法被final
修饰也会导致事务失效。
使用 '@Transactional'
注解的方法必须可重写
方法是final的
同理:使用 '@Transactional' 注解的方法必须可重写
单独的线程调用方法
问题描述:在一个方法中,新开启了一个线程,在线程内部执行 SQL 语句并抛出异常,但这些异常不会被回滚,事务注解无法管控到线程内部的 SQL 执行。 原因分析:新创建的线程相当于新创建了一个连接,而事务是与连接绑定的,连接不同事务也不同,因此线程内部的 SQL 执行结果不会被事务管理。
@Transactional
@Override
public void save2() {
Person person = new Person();
person.setName("name");
save(person);
new Thread(() -> {
Person person1 = new Person();
person1.setName("name1");
save(person1);
int i = 1 / 0;
}).start();
}