欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

使用@Transactional注解事务,可能出现哪些问题呢

程序员文章站 2022-07-14 23:04:03
...

前言

在之前的文章中已经对Spring中的事务做了详细的分析了,这篇文章我们来聊一聊平常工作时使用事务可能出现的一些问题(本文主要针对使用@Transactional进行事务管理的方式进行讨论)以及对应的解决方案

  • 事务失效
  • 事务回答相关问题
  • 读写分离跟事务结合使用时的问题

事务失效

事务失效我们一般要从两个方面排查问题

  1. 数据库层面
    数据库层面,数据库使用的存储引擎是否支持事务?默认情况下MySQL数据库使用的是Innodb存储引擎(5.5版本之后),它是支持事务的,但是如果你的表特地修改了存储引擎,例如,你通过下面的语句修改了表使用的存储引擎为MyISAM,而MyISAM又是不支持事务的

alter table table_name engine=myisam;

这样就会出现“事务失效”的问题了

解决方案:修改存储引擎为Innodb。

  1. 业务代码层面
    业务层面的代码是否有问题,这就有很多种可能了

我们要使用Spring的声明式事务,那么需要执行事务的Bean是否已经交由了Spring管理?在代码中的体现就是类上是否有@Service、Component等一系列注解

解决方案:将Bean交由Spring进行管理(添加@Service注解)

@Transactional注解是否被放在了合适的位置。在上篇文章中我们对Spring中事务失效的原理做了详细的分析,其中也分析了Spring内部是如何解析@Transactional注解的,我们稍微回顾下代码:

使用@Transactional注解事务,可能出现哪些问题呢
也就是说,默认情况下你无法使用@Transactional对一个非public的方法进行事务管理

解决方案:修改需要事务管理的方法为public。

出现了自调用。什么是自调用呢?我们看个例子

@Service
public class DmzService {
        public void saveAB(A a, B b) {
        saveA(a);        saveB(b);    }    @Transactional
    public void saveA(A a) {
        dao.saveA(a);    }        @Transactional
    public void saveB(B b){
        dao.saveB(a);    
     }
 }

上面三个方法都在同一个类DmzService中,其中saveAB方法中调用了本类中的saveA跟saveB方法,这就是自调用。在上面的例子中saveA跟saveB上的事务会失效

那么自调用为什么会导致事务失效呢?我们知道Spring中事务的实现是依赖于AOP的,当容器在创建dmzService这个Bean时,发现这个类中存在了被@Transactional标注的方法(修饰符为public)那么就需要为这个类创建一个代理对象并放入到容器中,创建的代理对象等价于下面这个类

public class DmzServiceProxy {
    private DmzService dmzService;
    public DmzServiceProxy(DmzService dmzService) {
        this.dmzService = dmzService;
    }    public void saveAB(A a, B b) {
        dmzService.saveAB(a, b);    }    public void saveA(A a) {
        try {
            // 开启事务
            startTransaction();
            dmzService.saveA(a);
        } catch (Exception e) {
            // 出现异常回滚事务
            rollbackTransaction();
        }
        // 提交事务
        commitTransaction();
    }
    public void saveB(B b) {
        try {
            // 开启事务
            startTransaction();
            dmzService.saveB(b);
        } catch (Exception e) {
            // 出现异常回滚事务
            rollbackTransaction();
        }
        // 提交事务
        commitTransaction();
    }
}

上面是一段伪代码,通过startTransaction、rollbackTransaction、commitTransaction这三个方法模拟代理类实现的逻辑。因为目标类DmzService中的saveA跟saveB方法上存在@Transactional注解,所以会对这两个方法进行拦截并嵌入事务管理的逻辑,同时saveAB方法上没有@Transactional,相当于代理类直接调用了目标类中的方法。

我们会发现当通过代理类调用saveAB时整个方法的调用链如下:

使用@Transactional注解事务,可能出现哪些问题呢

实际上我们在调用saveA跟saveB时调用的是目标类中的方法,这种情况下,事务当然会失效。

常见的自调用导致的事务失效还有一个例子,如下:

@Service
public class DmzService {
    @Transactional
    public void save(A a, B b) {
        saveB(b);    }        @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveB(B b){
        dao.saveB(a);  
     }
 }

当我们调用save方法时,我们预期的执行流程是这样的

使用@Transactional注解事务,可能出现哪些问题呢

也就是说两个事务之间互不干扰,每个事务都有自己的开启、回滚、提交操作。

但根据之前的分析我们知道,实际上在调用saveB方法时,是直接调用的目标类中的saveB方法,在saveB方法前后并不会有事务的开启或者提交、回滚等操作,实际的流程是下面这样的

使用@Transactional注解事务,可能出现哪些问题呢

由于saveB方法实际上是由dmzService也就是目标类自己调用的,所以在saveB方法的前后并不会执行事务的相关操作。这也是自调用带来问题的根本原因:自调用时,调用的是目标类中的方法而不是代理类中的方法

解决方案

自己注入自己,然后显示的调用,例如:

使用@Transactional注解事务,可能出现哪些问题呢

这种方案看起来不是很优雅

利用AopContext,如下:

使用@Transactional注解事务,可能出现哪些问题呢
使用上面这种解决方案需要注意的是,需要在配置类上新增一个配置

// exposeProxy=true代表将代理类放入到线程上下文中,默认是false
@EnableAspectJAutoProxy(exposeProxy = true)

写在最后

通过一张图总结事务失败的原因
使用@Transactional注解事务,可能出现哪些问题呢

如果觉得可以帮到你,记得关注我哦O(∩_∩)O哈哈~

使用@Transactional注解事务,可能出现哪些问题呢