跳到主要内容

事务失效问题

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 自调用(实际上是目标对象内的方法调用目标对象的另一个方法)在运行时不会导致实际的事务

image-20240901222448727

原因很简单:Spring 事务是基于 AOP 的,只有使用代理对象调用方法时,Spring 事务才会生效。在方法内直接使用this对象调用,相当于使用原始对象,而不是代理对象,导致事务失效。

下面我们通过调试来说明这个问题:

不使用代理,可以看到就是this来调用的

image-20240901224907389

使用代理,可以看到这是一个基于JDK动态代理的对象:

image-20240901225408313

解决办法:可以通过在 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' 注解的方法必须可重写

image-20240901225841449

方法是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();
}

异常未抛出

问题描述:在代码中,异常原本应该往外抛,但通过try-catch捕获了异常,导致 Spring 认为这是一个正常执行的结果,从而正常提交事务。 解决方法:确保异常能够被正确抛出,让 Spring 事务能够感知到异常并进行事务回滚。

@Transactional
@Override
public void save2() {
try {
Person person = new Person();
person.setName("name");
person.setPassword("password");
person.setEmail("email");
int i = 1 / 0;

} catch (Exception e) {
e.printStackTrace();
}
}

类没有被Spring管理

问题描述:如果在 Service 类上取消了 Spring 的注解注入,Spring 容器中就不会有该对象,自然也不会有其方法的调用,事务也就不会生效。 解决方法:确保需要事务管理的类被 Spring 容器正确管理。

// @Service
public class PersonServiceImpl extends ServiceImpl<PersonMapper, Person> implements PersonService {