spring事务原理

1、事务概念

事务,就是一组操作数据库的动作集合,要么全部成功,要么全部失败。

spring 支持两种方式的事务:

编程式事务

编程式事务管理使用 TransactionTemplate,需要显式执行事务,比如,需要显示调用commit或者rollback方法。

声明式事务

声明式事务管理建立在 AOP 之上的。其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。 优点是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过 @Transactional 注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

2、声明式事务

2.1、使用方式

使用@EnableTransactionManagement来开启spring事务功能(SpringBoot中不用显示设置)

使用@Transactional注解来标记某个方法使用事务功能,作用在类上则所有方法都使用事务

2.2、事务实现原理

(1)@EnableTransactionManagement做了什么?

开启Spring事务本质上就是增加了一个Advisor,但我们使用@EnableTransactionManagement注解来开启Spring事务是,该注解代理的功能就是向Spring容器中添加了两个Bean:

  • AutoProxyRegistrar

  • ProxyTransactionManagementConfiguration

AutoProxyRegistrar主要的作用是向Spring容器中注册了一个InfrastructureAdvisorAutoProxyCreator的Bean。而InfrastructureAdvisorAutoProxyCreator继承了AbstractAdvisorAutoProxyCreator,所以这个类的主要作用就是开启自动代理的作用,也就是一个BeanPostProcessor,会在初始化后步骤中去寻找Advisor类型的Bean,并判断当前某个Bean是否有匹配的Advisor,是否需要利用动态代理产生一个代理对象。

ProxyTransactionManagementConfiguration是一个配置类,它又定义了另外三个bean:

  • BeanFactoryTransactionAttributeSourceAdvisor:一个Advisor

  • AnnotationTransactionAttributeSource:

    相当于BeanFactoryTransactionAttributeSourceAdvisor中的Pointcut

  • TransactionInterceptor:

    相当于BeanFactoryTransactionAttributeSourceAdvisor中的Advice

AnnotationTransactionAttributeSource就是用来判断某个类上是否存在@Transactional注解,或者判断某个方法上是否存在@Transactional注解的。

TransactionInterceptor就是代理逻辑,当某个类中存在@Transactional注解时,到时就产生一个代理对象作为Bean,代理对象在执行某个方法时,最终就会进入到TransactionInterceptor的invoke()方法。

(2)spring事务的基本执行原理

一个Bean在执行Bean的创建生命周期时,会经过InfrastructureAdvisorAutoProxyCreator的初始化后的方法,会判断当前Bean对象是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,匹配逻辑为判断该Bean的类上是否存在@Transactional注解,或者类中的某个方法上是否存在@Transactional注解,如果存在则表示该Bean需要进行动态代理产生一个代理对象作为Bean对象。

该代理对象在执行某个方法时,会再次判断当前执行的方法是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,如果匹配则执行该Advisor中的TransactionInterceptor的invoke()方法,执行基本流程为(单一事务):

  • 1、利用所配置的PlatformTransactionManager事务管理器新建一个数据库连接conn

这个连接保存在一个ThreadLocal<Map<DateSource,conn>>,key是数据源,value就是连接coon

  • 2、修改数据库连接的autocommit为false,并设置@Transactional其他属性配置

  • 3、执行MethodInvocation.proceed()方法,简单理解就是执行业务方法,其中就会执行sql

  • 4、如果没有抛异常,则提交

  • 5、如果抛了异常,则回滚

注意:这里不能由JDBCTemplate或Mybatis自己建立连接,否则就无法用到事务了,spring处理多事务也是通过建立不同连接来实现的。

3、事务传播机制

3.1、入门案例

先来看一种情况,a()在一个事务中执行,调用b()方法时需要新开一个事务执行的情况分析:

  • 首先,代理对象执行a()方法前,先利用事务管理器新建一个数据库连接a

  • 将数据库连接a的autocommit改为false,并设置其他属性

  • 把数据库连接a设置到ThreadLocal中

  • 执行a()方法中的sql

  • 执行a()方法过程中,调用了b()方法,需要新开一个事务(注意用代理对象调用b()方法)

这里有一个AOP的知识点:如果不用代理对象调,在类中调的话,则是调用的普通对象的b()方法,这是不会走代理逻辑的,所以也就用不到事务。

  • 代理对象执行b()方法前,判断出来了当前线程中已经存在一个数据库连接a了,表示当前线程其实已经拥有一个Spring事务了,则进行挂起

  • 挂起就是把ThreadLocal中的数据库连接a从ThreadLocal中移除,并放入一个挂起资源对象中

  • 挂起完成后,再次利用事务管理器新建一个数据库连接b,将数据库连接b的autocommit改为false

  • 把数据库连接b设置到ThreadLocal中,执行b()方法中的sql

  • b()方法正常执行完,则从ThreadLocal中拿到数据库连接b进行提交

  • 提交之后会恢复所挂起的数据库连接a,这里的恢复,其实只是把在挂起资源对象中所保存的数据库连接a再次设置到ThreadLocal中

  • a()方法正常执行完,则从ThreadLocal中拿到数据库连接a进行提交

这个过程中最为核心的是:在执行某个方法时,判断当前是否已经存在一个事务,就是判断当前线程的ThreadLocal中是否存在一个数据库连接对象,如果存在则表示已经存在一个事务了。

3.2、七大事务传播属性

  • PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

  • PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。

  • PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。

  • PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。

  • PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

  • PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。

  • PROPAGATION_NESTED – 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

备注:常用的两个事务传播属性是1和4,即PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW

其中,以非事务方式运行,表示以非Spring事务运行,表示在执行这个方法时,Spring事务管理器不会去建立数据库连接,执行sql时,由Mybatis或JdbcTemplate自己来建立数据库连接来执行sql。

3.3、案例分析

(1)情况1

@Component
public class UserService {
  @Autowired
  private UserService userService;
 
  @Transactional
  public void test() {
    // test方法中的sql
    userService.a();
  }
 
  @Transactional
  public void a() {
    // a方法中的sql
  }
}

默认情况下传播机制为REQUIRED,表示当前如果没有事务则新建一个事务,如果有事务则在当前事务中执行。

所以上面这种情况的执行流程如下:

  • 新建一个数据库连接conn

  • 设置conn的autocommit为false

  • 执行test方法中的sql

  • 执行a方法中的sql

  • 执行conn的commit()方法进行提交

(2)情况2

@Component
public class UserService {
  @Autowired
  private UserService userService;
 
  @Transactional
  public void test() {
    // test方法中的sql
    userService.a();
        int result = 100/0;
  }
 
  @Transactional
  public void a() {
    // a方法中的sql
  }
}

所以上面这种情况的执行流程如下:

  • 新建一个数据库连接conn

  • 设置conn的autocommit为false

  • 执行test方法中的sql

  • 执行a方法中的sql

  • 抛出异常

  • 执行conn的rollback()方法进行回滚,所以两个方法中的sql都会回滚掉

(3)情况3

@Component
public class UserService {
  @Autowired
  private UserService userService;
 
  @Transactional
  public void test() {
    // test方法中的sql
    userService.a();
  }
 
  @Transactional
  public void a() {
    // a方法中的sql
        int result = 100/0;
  }
}

所以上面这种情况的执行流程如下:

  • 新建一个数据库连接conn

  • 设置conn的autocommit为false

  • 执行test方法中的sql

  • 执行a方法中的sql

  • 抛出异常

  • 执行conn的rollback()方法进行回滚,所以两个方法中的sql都会回滚掉

(4)情况4

@Component
public class UserService {
  @Autowired
  private UserService userService;
 
  @Transactional
  public void test() {
    // test方法中的sql
    userService.a();
  }
 
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void a() {
    // a方法中的sql
    int result = 100/0;
  }
}

所以上面这种情况的执行流程如下:

  • 新建一个数据库连接conn

  • 设置conn的autocommit为false

  • 执行test方法中的sql

  • 又新建一个数据库连接conn2

  • 执行a方法中的sql

  • 抛出异常

  • 执行conn2的rollback()方法进行回滚

  • 继续抛异常,对于test()方法而言,它会接收到一个异常,然后抛出

  • 执行conn的rollback()方法进行回滚,最终还是两个方法中的sql都回滚了

3.4、事务失效的场景

(1)@Transactional作用在非public修饰的方法上

如果Transactional注解应用在非 public 修饰的方法上,Transactional将会失效。

是因为在Spring AOP 代理时,TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的intercept方法 或 JdkDynamicAopProxy的invoke方法会间接调用AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute方法,获取Transactional 注解的事务配置信息。

protected TransactionAttribute computeTransactionAttribute(Method method,Class<?> targetClass) {
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
}

此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息

(2)@Transactional作用在使用final或者static修饰的方法

Spring事务底层使用了AOP,也就是通过JDK动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,从而无法添加事务功能。这种情况事务就会在Spring中失效。

根据这个原理可知,如果某个方法是static的,同样无法通过动态代理将方法声明为事务方法。

(3)@Transactional 注解属性propagation设置错误

传播属性设置为非事务方式。

TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

(4)同一个类中方法调用导致@Transactional失效

这里有一个AOP的知识点:如果不用代理对象调,在类中调的话,则是调用的普通对象的b()方法,这是不会走代理逻辑的,所以也就用不到事务。

(5)多线程情况

情况一:父线程抛异常,子线程OK

父线程抛出线程,事务回滚,因为子线程是独立存在,和父线程不在同一个事务中,所以子线程的修改并不会被回滚

情况二:父线程OK,子线程抛异常

由于子线程的异常不会被外部的线程捕获,所以父线程不抛异常,事务回滚没有生效。

 

参考文章:https://blog.csdn.net/u012060033/article/details/87911330

posted @ 2024-05-18 23:19  jingyi_up  阅读(184)  评论(0编辑  收藏  举报