Loading

Spring 源码学习笔记11——Spring事务

Spring事务是基于Spring Aop的扩展
AOP的知识参见《Spring 源码学习笔记10——Spring AOP》
图片参考了https://www.processon.com/view/60f4d859e0b34d0e1b6bb40c?fromnew=1
逻辑事务和物理事务的概念来自https://wiyi.org/physical-and-logical-transactions.html

本文忽略了编程式事务,探究了基于事务注解的申明式事务

系列文章目录和关于我

一丶spring事务相关概念

事务是指访问并更新数据库中各种数据项的一个程序执行单元,在事务中的操作,要么都做修改,要么都不做。

1.事务传播级别

事务传播级别决定了事务的控制范围,这似乎不是数据库层面的概念。

1.1.required

Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行

image-20220828194926937

1.2.supports

如果上下文中存在事务则加入当前事务,如果没有事务则以非事务方式执行

image-20220828195032146

1.3.mandatory

该传播级别要求上下文中必须存在事务,否则抛出异常

image-20220828195212730

1.4.requires_new

该传播级别每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。(子事务的执行结果不影响父事务的执行和回滚)但是内部调用的方法抛出异常,也会导致外部回滚

image-20220828195316767

1.5.not_supported

当上下文中有事务则挂起当前事务,执行完当前逻辑后再恢复上下文事务。(降低事务大小,将非核心的执行逻辑包裹执行。)

image-20220828195500957

1.6.never

该传播级别要求上下文中不能存在事务,否则抛出异常。

image-20220828195830988

1.7.nested

嵌套事务,如果上下文中存在事务则嵌套执行,如果不存在则新建事务

image-20220828195614914

2. 物理事务和逻辑事务

2.1物理事务

所谓物理事务指的就是Connection开启的事务。

2.2逻辑事务

在一个复杂的业务系统中,可能会调用多个service,每个service都有自己的事务(标注了@Transactional),此时我们需要根据事务传播方式(Propagation)来决定当前事务的行为(比如要挂起创建新事物,还是加入当前事务)。

我们可以认为每个注解了@Transactional的方法都是一个逻辑事务,这些逻辑事务被Spring事务管理,Spring会根据事务传播方式来决定是否开启新事务。

二丶@EnableTransactionManagement注解做了什么

在SpringBoot使用事务的时候我们通常会在配置类上面加一个@EnableTransactionManagement注解来开启事务,其实不加也可以,SpringBoot有事务的AutoConfiguration。下来我们看下这个注解干了什么。

image-20220827165625589

重点在于@Import(TransactionManagementConfigurationSelector.class)TransactionManagementConfigurationSelector实现了ImportSelector.最终会执行selectImports为容器注入bean。

image-20220827165832304

这里会注入AutoProxyRegistrarProxyTransactionManagementConfiguration.接下来我们看些这个两个类有什么作用

1.AutoProxyRegistrar

AutoProxyRegistrar实现了ImportBeanDefinitionRegistrar,会向容器中注入需要的BeanDefinition,这里最终调用了AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry),如果容器中没有名称为org.springframework.aop.config.internalAutoProxyCreator的BeanDefinition那么会注入InfrastructureAdvisorAutoProxyCreator

image-20220827170731826

InfrastructureAdvisorAutoProxyCreatorAbstractAutoProxyCreator的子类,实现了SmartInstantiationAwareBeanPostProcessor会在getEarlyBeanReference提前暴露代理对象或者在bean完成实例化初始化后调用postProcessAfterInitialization对原始的bean进行aop增强,这部分我们在Spring 源码学习笔记10——Spring AOP 已经学习过了。也就是说这里会注入一个基于动态代理实现Aop增强的一个bean后置处理器

2.ProxyTransactionManagementConfiguration

image-20220827171702658

  1. 注入TransactionalEventListenerFactory

    这一步是在其父类AbstractTransactionManagementConfiguration中完成的

    TransactionalEventListenerFactorySpring源码学习笔记5——注册BeanPostProcessor,初始化事件多播器,注册事件监听器中我们提到过,它会把标注TransactionalEventListener的方法包装成一个ApplicationListener来响应对应的事件。

  2. 注入TransactionInterceptor

    image-20220827172419345

    TransactionInterceptor是一个Advise一个基于方法拦截器的通知,在其中实现了事务的增强逻辑。Spring 源码学习笔记10——Spring AOP 中我们分析过最后的aop代理对象执行方法的时候会调用到方法拦截器的invoke方法,TransactionInterceptor便是在其中实现了事务的增强。

  3. 注入BeanFactoryTransactionAttributeSourceAdvisor

    image-20220827172711146

    BeanFactoryTransactionAttributeSourceAdvisor是一个PointcutAdvisor,是一个Advisor,它使用Pointcut来判断当前对象的方法是否需要增强,使用Advice对方法进行增强。

  4. 注入TransactionAttributeSource

    这里注入的是AnnotationTransactionAttributeSource,主要负责从类上,方法上,获取@Transactional注解信息,比如抛出什么异常回滚,事务超时时间等。

看完这个类,我们基本上清楚了Spring 事务是如何实现的了,首先是通过BeanPostProcessor与Spring IOC容器结合在一起,在bean实例化初始化后调用后置处理器(如果出现循环依赖那么调用的是提前暴露对象的getEarlyBeanReference)对bean进行Aop增强,在AbstractAutoProxyCreatorwrapIfNecessary方法中,会获取全部的Advisor,便会拿到注入的BeanFactoryTransactionAttributeSourceAdvisor,然后使用Pointcut进行过滤,然后通过ProxyFactory选择使用JDK动态代理,还是cglib动态代理。后续调用代理对象的方法会调用到,TransactionInterceptor中的invoke实现了事务增强的逻辑。接下来我们详细看看细节部分

三丶Spring事务源码分析

1.BeanFactoryTransactionAttributeSourceAdvisor

实现了PointcutAdvisor接口,我们看下它的Pointcut到底是什么,它的Advice又是什么。

1.1 Pointcut

image-20220827175133434

BeanFactoryTransactionAttributeSourceAdvisor维护了一个TransactionAttributeSourcePointcut类型的pointcut

image-20220827175302979

它实现了StaticMethodMatcherPointcut,它是一个静态方法匹配的Pointcut,这里的静态意思是不会因为入参的参数不同而改变过滤结果。我们看下具体的实现逻辑。

image-20220827175505813

这里的getTransactionAttributeSource返回的是AnnotationTransactionAttributeSource实例,这里的逻辑是只要有TransactionAttributeSource并且可以拿到事务定义信息,那么就视为匹配,后面就会对方法进行增强。

1.2 AnnotationTransactionAttributeSource是如何获取事务定义信息的

getTransactionAttribute由其父类AbstractFallbackTransactionAttributeSource实现,其使用一个ConcurrentHashMap缓存方法和其对应的事务信息,如果缓存中没有那么会调用computeTransactionAttribute方法进行获取。computeTransactionAttribute方法会优先获取方法上面的事务注解信息,然后获取类类上面的注解信息。

image-20220827181715474

可以看到事务注解只能标注在public方法上面,如果是像mybatis这种生成接口动态代理类那么会拿到接口上面的注解信息。

获取注解信息调用了子类的AnnotationTransactionAttributeSource#determineTransactionAttribute方法

image-20220827181911646

这里涉及到一个新的类TransactionAnnotationParser,这是Spring根据AnnotatedElement获取注解的接口,可以扩展此接口实现自己的事务注解,并定制事务定义信息。这些事务注解解析器在构造方法中进行了定义。

image-20220827182114532

可以看到Spring内置了JTA,ejb的事务注解处理器,但是使用的是LinkedHashSet,SpringTransactionAnnotationParser会放在最前面,解析Spring的·@Transactional·注解,这也是我们最常用的事务注解,SpringTransactionAnnotationParser会拿到事务注解信息,然后把注解的属性内容包装成 RuleBasedTransactionAttribute

image-20220827182759170RuleBasedTransactionAttributerollbackOn进行了扩展,配合SpringTransactionAnnotationParser会解析事务注解中的rollbackFor,rollbackForClassName,noRollbackFor,noRollbackForClassName属性来判断异常抛出时是否需要回滚事务。如果这个属性没有值的话,会调用父类的rollbackOn,最终只会在RuntimeExceptionError上面回滚。

1.3 BeanFactoryTransactionAttributeSourceAdvisor是如何获取到Advice

它实现了BeanFactoryAware,使用advice持有当前通知的引用,如果没有那么从容器中根据名称拿。但是在ProxyTransactionManagementConfiguration中直接设置advice为容器中的TransactionInterceptor,也就是说事务的增强逻辑定义在TransactionInterceptor中。

2.TransactionInterceptor

TransactionInterceptor是事务拦截器,其invoke方法会在代理对象被代理方法执行的时候被回调到

image-20220828151711285

其中invokeWithinTransaction是spring事务原理的关键,此方法在其父类TransactionAspectSupport实现

2.1 利用TransactionAttributeSource获取方法或者类上的事务注解信息

事务注解决定了事务增强的代码执行逻辑,这里使用的TransactionAttributeSource通常是是上文中我们提到的AnnotationTransactionAttributeSource,上文中我们直到,其内部存在map缓存,如果缓存没有那么调用TransactionAnnotationParser链式的进行解析,自然就调用到了SpringTransactionAnnotationParser,SpringTransactionAnnotationParser反射获取注解信息包装成TransactionAttribute对象

2.2 获取事务管理器PlatformTransactionManager

PlatformTransactionManager是spring抽象出的事务管理器接口,主要包含下面三个方法

public interface PlatformTransactionManager {
    //根据指定的传播行为返回当前活动的事务或创建新事务。
   TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
    //根据事务状态提交事务
   void commit(TransactionStatus status) throws TransactionException;
  //根据事务状态回滚事务
    void rollback(TransactionStatus status) throws TransactionException;
   }

具体实现我们后续遇到对应方法再说。

determineTransactionManager方法决定使用什么事务管理器image-20220828153636200

这里可以看到Spring是支持@Transactional注解value指定特定的事务管理器,但是我们实际使用中通常是没有指定的,这里就会去bean工程中获取PlatformTransactionManager类型的bean,存在多个就会抛出异常了。这里拿到的一般是DataSourceTransactionManager

2.3 根据事务传播级别来决定是否由必要创建事务createTransactionIfNecessary

首先调用getTransaction方法获取一个事务,然后调用prepareTransactionInfo封装事务的处理配置信息并绑定到当前线程

其中getTransactionAbstractPlatformTransactionManager中是一个final的模板方法,它首先判断是否存在一个事务,如果存在那么调用handleExistingTransaction来创建事务,如果不存在那么根据事务传播级别来决定是否创建事务。

  • 不存在事务的情况

    image-20220828160518600

    首先要求超时时长不能小于-1.-1表示的使用底层事务系统的默认超时,如果不支持超时,则使用无

    image-20220828160632404

    然后如果的传播级别是MANDATORY支持当前事务,如果不存在则抛出异常,也就是说MANDATORY要求外层调用方法是在一个具备事务的情况下进行的调用.

    如果隔离级别是Required(支持当前事务,如果当前不存在事务那么创建一个新事务),RequireNew(创建一个新事务,如果存在事务则暂停当前事务),Nested(如果当前事务存在,则在嵌套事务中执行)那么会执行下面的逻辑

    image-20220828165954403

    首先是挂起当前事务,由于当前不存在事务,其实是把当前存在TransactionSynchronization事务同步回调的接口信息保存在SuspendedResourcesHolder中。在DataSourceTransactionManagerdoBegin方法会获取对应的Connection,然后根据事务定义对Connection进行设置,比如如果是只读事务那么会执行SET TRANSACTION READ ONLY设置事务只读,设置超时时长,关闭自动提交等,并且把创建的事务信息绑定到resources ThreadLocal中。

    image-20220828172213867

    prepareSynchronization负责把事务相关信息设置到ThreadLocal中,并且初始化TransactionSynchronizationLinkedHashSet,这样我们我们通过TransactionSynchronizationManager加入一些回调方法的时候不会抛NPE,之所以使用LinkedHashSetTransactionSynchronizationManager支持Ordered接口,@Order注解进行排序

    如果是 SUPPORTS(支持当前事务,如果不存在,则以非事务方式执行),NOT_SUPPORTED(不支持当前事务;始终以非事务方式执行) ,NEVER(不支持当前事务,如果当前事务存在,则抛出异常)那么

    不会开启事务,但是支持事务同步

    image-20220828174150033

  • 存在事务的情况

    1. 是如何判断是否存在事务的

      首先doGetTransaction方法会获取resources ThreadLocal中的事务信息,这个事务信息是在doBegin方法中绑定的

      image-20220828173006425

      然后调用isExistingTransaction来判断是否存在事务,

      image-20220828173504825

      同样doBegin方法会设置transactionActive为true。
      最后如果存在事务那么会执行下面的handleExistingTransaction来根据事务传播级别来创建事务

    2. handleExistingTransaction

      首先如果传播级别是NEVER(不支持当前事务,如果当前事务存在,则抛出异常)当前存在事务,那么抛出异常

      image-20220828173706655

      如果传播级别是NOT_SUPPORTED(不支持当前事务;始终以非事务方式执行)那么

      不会开启事务,但是支持事务同步,并且会挂起当前事务,其中prepareTransactionStatus会初始化事务同步set,并且把当前事务的信息包装到DefaultTransactionStatus并返回

      image-20220828174623541

      如果传播级别是RequireNew(创建一个新事务,如果存在事务则暂停当前事务)会挂起当前事务,从ThreadLocal中解绑,然后开启新事务,并初始化TransactionSynchronization`的set

      image-20220828175123533

      如果传播级别是Nested(如果当前事务存在,则在嵌套事务中执行),首先会判断是否允许嵌套事务,如果不允许那么抛出异常,通常DataSourceTransactionManager使用savepoint来实现嵌套事务

      image-20220828180001030

      调用Connection#setSavepoint方法

      image-20220828180246950

      最后如果是Required(支持当前事务,如果当前不存在事务那么创建一个新事务) SUPPORTS(支持当前事务;如果不存在,则以非事务方式执行) MANDATORY(支持当前事务;如果当前不存在事务,则抛出异常)

      image-20220828180524790

      都不会产生新的事务。

2.4 prepareTransactionInfo包装事务信息并绑定到ThreadLocal

image-20220828182846133

image-20220828182856158

这里会使用oldTransactionInfo记录之前的事务信息,并且绑定当前事务信息到ThreadLocal上,这样A事务方法调用B事务方法的时,能像栈一样先进后出。在invokeWithinTransaction调用完业务方法后,会调用cleanupTransactionInfooldTransactionInfo重写设置到ThreadLocal中,这意味着B方法执行结束,回到了A方法的调用栈中。

2.5 回调业务逻辑InvocationCallback#proceedWithInvocation

其实调用的是MethodInvocation#proceed,如果当前对象存在多层代理,比如先事务代理,再基于@AspectJ注解的方法调用时长统计,那么后面代理增强也会执行,具体逻辑在ReflectiveMethodInvocation#proceed方法中,如果还存在其他的拦截器链那么会继续执行拦截器中的逻辑,否则直接执行我们自己的业务逻辑代码,这部分在Spring 源码学习笔记10——Spring AOP 说到过。

2.6 completeTransactionAfterThrowing 业务逻辑出现异常时的处理

image-20220828184723030

这里rollbackOn取决于事务注解上面标注的在什么异常上回滚,在什么异常上不回滚,默认是在RuntimeExceptionError上面才会回滚。

下面我们看下是如何回滚事务的

image-20220828185031019

image-20220828190012403

执行回滚DataSourceTransactionManager是调用的Connection#rollback方法,这里首先会获取ThreadLocal中的TransactionSynchronization并且按照@OrderOrdered排序然后依次调用beforeCompletion,如果具备回滚点,那么直接回滚到保存点,如果是一个新事务,那么直接回滚,如果不是一个独立的事务,只是标记需要回滚,执行完这些后,还会回调triggerAfterCompletion,排序然后调用TransactionSynchronization#afterCompletion方法,然后调用cleanupAfterCompletion清理资源,并且恢复被挂起的线程。

2.7 commitTransactionAfterReturning提交事务

具体逻辑在processCommit中进行

image-20220828191531751

DataSourceTransactionManager提交事务是执行的是Connection#commit

同样完成只会还会调用triggerAfterCommit,triggerAfterCompletion并清理ThreadLocal中的内容,并且恢复被挂起的事务。

3.事务同步回调接口TransactionSynchronization

image-20220828192354727

其中beforeCommit,beforeCompletion,aterCommitafterCompletion,只有当前事务时一个独立事务的时候才会回调,而且如果提交或者回滚的时候出现异常,beforeCompletion,afterCompletion也会被调用,我们可以通过TransactionSynchronizationManager#registerSynchronization注册回调的逻辑。

image-20220828193252799

那么什么时候事务被视作时一个独立事务昵

  • 如果外部不存在一个事务,并且传播级别是REQUIRED,REQUIRES_NEW,NESTED
  • 如果外部存在一个事务,且传播级别为REQUIRES_NEW
  • 如果外部存在一个事务,且传播级别为嵌套事务,但是此时不是通过保存点来实现嵌套事务
posted @ 2022-08-28 20:06  Cuzzz  阅读(304)  评论(0编辑  收藏  举报