SpringBoot事务管理

前言

  好久没有写博客了,最近终于能沉下心来阅读底层源码了(主要是上班时间比较闲(●'◡'●))。本文部分内容涉及spring容器没有细讲,有兴趣的同学可以去看我容器启动分析的文章(写的第一篇文章比较烂,有时间应该会重新整理下)。

springboot自动配置基本原理

  SpringBoot首先在启动的时候会读取项目下的"META-INF/spring.factories"路径的自动配置类,过滤器,监听器,ApplicationContextInitializer等。mybatis的自动装配就引入了MybatisAutoConfiguration类。
  分析mybatis的自动装配前需要来分析下@EnableAutoConfiguration注解,SpringBoot的启动注解@SpringBootApplication就自带该注解,该注解引入了两个类分别是AutoConfigurationImportSelector,AutoConfigurationPackages.Registrar。

  1. AutoConfigurationImportSelector: 该类实现了DeferredImportSelector接口(继承ImportSelector接口),所以在spring进行容器刷新的时候会调用该类的selectImports方法将一些组件注册进容器当中。 这个类的selectImports方法:
    1. 从项目中读取"META-INF/spring-autoconfigure-metadata.properties"文件的内容(自动装配引入的类)。
    2. 从缓存中获取EnableAutoConfiguration类的类名(SpringBoot启动时读取的自动配置类);
    3. 移除配置的忽略类(@SpringBootApplication/@EnableAutoConfiguration的exclude属性配置)
    4. 使用过滤器筛选自动配置类。SpringBoot默认注册了OnClassCondition过滤器,在spring-autoconfigure-metadata.properties的配置文件中需要配置ConditionalOnClass对应的value就是OnClassCondition类去判断的类)该过滤器筛选掉ClassLoader中缺失所需类的自动装配类。
  2. AutoConfigurationPackages.Registrar:将AutoConfigurationPackages类注册入容器,目前还不清楚该类的作用。官方描述:Class for storing auto-configuration packages for reference later (e.g. by JPA entity scanner).
    SpringBoot读取自动装配类流程图如下:
    img

事务相关的自动配置(JDBC)

  事务相关的自动配置需要关注的有三个类,第一个是@EnableTransactionManagementspringboot用于开启事务的注解,其他两个类是通过spring.factories读取的配置类分别是TransactionAutoConfigurationDataSourceTransactionManagerAutoConfiguration这些类用于向spring容器中注入一些事务相关的关键类。(这里分析的是JDBC方向的,JTA原理类似)

DataSourceTransactionManagerAutoConfiguration

img

  DataSourceTransactionManagerAutoConfiguration该类主要向容器注入PlatformTransactionManager类,这边注入的是DataSourceTransactionManager(注意的是这个类是自动注入最低优先级的,应该是考虑到让DataSource等先注册)

img

TransactionAutoConfiguration

  TransactionAutoConfiguration该类主要向容器注入TransactionTemplate和支持spring的事务管理只要容器中有PlatformTransactionManager类就会自动开启事务。所以很多教程springboot开启事务管理需要添加@EnableTransactionManagement注解,其实完全是多余的!只要你在容器中注入有spring的事务管理器就ok了。(springboot的jdbc-starter和jta-starter都帮我们注入了事务管理器)

EnableTransactionManagement

  该类就使用@Import向容器注入了TransactionManagementConfigurationSelector类,该类实现了ImportSelector 接口,向容器注入了AutoProxyRegistrar和ProxyTransactionManagementConfiguration类(默认的Proxy)。重点要看的是ProxyTransactionManagementConfiguration类。

ProxyTransactionManagementConfiguration

  ProxyTransactionManagementConfiguration实现了AbstractTransactionManagementConfiguration类。该类有个setConfigurers方法,该方法会将TransactionManagementConfigurer的实现类返回的PlatformTransactionManager返回赋值给TransactionInterceptor用于多事务管理器的默认管理。
  ProxyTransactionManagementConfiguration像spring容器添加了三个了:

  1. TransactionalEventListenerFactory,事务的监听器,这个不管。
  2. AnnotationTransactionAttributeSource,该类用于解析类或方法上的事务注解。
  3. TransactionInterceptor,该类是事务起作用的核心类,当执行事务切面的时候就是执行该类的invoke方法
  4. BeanFactoryTransactionAttributeSourceAdvisor,该类在aop生成代理类的时候会作为管理类被加入到aop的拦截器链用于触发回调,执行事务操作。该类的TransactionAttributeSourcePointcut属性在创建代理对象的时候调用match方法扫描该类及其所有方法,来确定该类是否需要被该拦截器处理。(aop的执行可以看我另外一篇博客)。

事务原理分析

  主要看TransactionInterceptor的invoke方法,前面也说到当执行到代理对象的方法时会获取该方法的所有拦截器来进行处理。事务添加的就是TransactionInterceptor拦截器。
  invokeWithinTransaction方法: 下图可以很明显的看出四步

  1. 开启事务: createTransactionIfNecessary
  2. 执行方法:processedWithInvocation
  3. 如果出错回滚:completeTransactionAfterThrowing
  4. 提交事务:commitTransactionAfterReturning
createTransactionIfNecessary

   关键PlatformTransactionManager.getTransaction();本质操作就是获取数据库连接,jdbc环境使用的就是DataSourceTransactionManager。简单讲一下就是根据事务的传播机制获取连接,然后根据readOnly、隔离级别等给连接设置属性,还有对事务状态等控制。

processedWithInvocation

   执行该类的方法,通过java8的FunctionalInterface将方法传入调用。

completeTransactionAfterThrowing

   DataSourceTransactionManager的doRollback,最终调用的就是connection.rollback()(如果异常是Error||RuntimeException)。其他还对事务的状态进行校验,获取savepoint,判断是否为嵌套事务等。

commitTransactionAfterReturning

   DataSourceTransactionManager的doCommit,最终调用的就是connection.commit(),如果是嵌套继承的事务则不会提交。

img

附录:

事务并发引发问题

脏读

    事务1 update data 1 > 2
    事务2 read data 2
    事务1 rollback data 2 > 1
    事务1 commit
    事务1 return data 2
复制代码

丢失修改

    事务1 read data 1
    事务2 read data 1
    事务1 update data 1 + 1 > 2
    事务2 update data 1 + 1 > 2
    事务1 commit
    事务2 commit
复制代码

不可重复读

    事务1 read data 1
    事务2 updata data 1 > 2
    事务2 commit
    事务1 read data 2
复制代码

幻读

   事务1 read data num 2
   事务2 insert delete num 2 > 1
   事务2 commit
   事务1 commit
   事务1 return data num 2
复制代码

事务隔离级别

read uncommitted

一个事务可以读取另一个事务未提交事务的数据。
读未提交会引起脏读,不可重复读、幻读、丢失修改
复制代码

read committed (orcale默认)

一个事务可以读取到另一个事务提交的数据。
读已提交解决了脏读,但还是会引起不可重复读、幻读、丢失修改。
复制代码

repeatable read (mysql默认)

可重复读。一个事务对同一份数据读取到的相同,不在乎其他事务对数据的修改。
解决不可重复读。(实现机制是在事务A读取之后将这些数据添加行级锁,其他事务无法修改:待验证)
会产生幻读,丢失修改。
丢失修改可通过数据库互斥锁(悲观锁)来解决,
或者在字段中加入版本号(乐观锁),也可以代码中自行加锁。如果这个级别自动在查询后添加行级锁,那么其他修改无法执行,就能解决丢失修改问题。
复制代码

serializable

严格的数据隔离,要求事务序列化执行,事务只能一个接一个执行。
严重影响并发能力。
解决所有并发事务引起问题。
复制代码

spring事务管理

隔离级别

TransactionDefinition接口中定义了五个表示隔离级别的常量:

  1. TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
  2. TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  3. TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  4. TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务传播机制

TransactionDefinition接口中定义了五个表示隔离级别的常量:

支持当前事务的情况:

  1. TransactionDefinition.PROPAGATION_REQUIRED:
    如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  2. TransactionDefinition.PROPAGATION_SUPPORTS:
    如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  3. TransactionDefinition.PROPAGATION_MANDATORY:
    如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

不支持当前事务的情况:

  1. TransactionDefinition.PROPAGATION_REQUIRES_NEW:
    创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  2. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
    以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  3. TransactionDefinition.PROPAGATION_NEVER:
    以非事务方式运行,如果当前存在事务,则抛出异常。

其他情况:

  1. TransactionDefinition.PROPAGATION_NESTED:
    如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

  这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED 是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。

事务超时属性

  所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

事务只读属性

  事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。

回滚规则

  这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)。 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

总结

  Spring通过良好的api将事务管理和数据源封装了起来,让我们可以很轻松的使用不同环境中的事务,但我觉得Spring事务管理这块扩展性还是略微有些差的,例如我原先想将事务支持多线程同一事务,原以为只要将每个线程的保存的连接统一即可,但随后发现必须要重写拦截器才能实现,Spring拦截器不对外修改事务状态以及一些处理的覆盖。