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的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行

1.2.supports

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

1.3.mandatory

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

1.4.requires_new

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

1.5.not_supported

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

1.6.never

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

1.7.nested

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

2. 物理事务和逻辑事务

2.1物理事务

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

2.2逻辑事务

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

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

二丶@EnableTransactionManagement注解做了什么

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

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

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

1.AutoProxyRegistrar

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

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

2.ProxyTransactionManagementConfiguration

  1. 注入TransactionalEventListenerFactory

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

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

  2. 注入TransactionInterceptor

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

  3. 注入BeanFactoryTransactionAttributeSourceAdvisor

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

  4. 注入TransactionAttributeSource

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

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

三丶Spring事务源码分析

1.BeanFactoryTransactionAttributeSourceAdvisor

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

1.1 Pointcut

BeanFactoryTransactionAttributeSourceAdvisor维护了一个TransactionAttributeSourcePointcut类型的pointcut

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

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

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

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

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

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

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

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

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

1.3 BeanFactoryTransactionAttributeSourceAdvisor是如何获取到Advice

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

2.TransactionInterceptor

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

其中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方法决定使用什么事务管理器

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

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

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

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

  • 不存在事务的情况

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

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

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

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

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

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

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

  • 存在事务的情况

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

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

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

      同样doBegin方法会设置transactionActive为true。

      最后如果存在事务那么会执行下面的handleExistingTransaction来根据事务传播级别来创建事务

    2. handleExistingTransaction

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

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

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

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

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

      调用Connection#setSavepoint方法

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

      都不会产生新的事务。

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

这里会使用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 业务逻辑出现异常时的处理

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

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

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

2.7 commitTransactionAfterReturning提交事务

具体逻辑在processCommit中进行

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

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

3.事务同步回调接口TransactionSynchronization

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

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

  • 如果外部不存在一个事务,并且传播级别是REQUIRED,REQUIRES_NEW,NESTED
  • 如果外部存在一个事务,且传播级别为REQUIRES_NEW
  • 如果外部存在一个事务,且传播级别为嵌套事务,但是此时不是通过保存点来实现嵌套事务

Spring 源码学习笔记11——Spring事务的更多相关文章

  1. Spring 源码学习笔记10——Spring AOP

    Spring 源码学习笔记10--Spring AOP 参考书籍<Spring技术内幕>Spring AOP的实现章节 书有点老,但是里面一些概念还是总结比较到位 源码基于Spring-a ...

  2. Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点

    Spring源码学习笔记12--总结篇,IOC,Bean的生命周期,三大扩展点 参考了Spring 官网文档 https://docs.spring.io/spring-framework/docs/ ...

  3. Spring源码学习笔记9——构造器注入及其循环依赖

    Spring源码学习笔记9--构造器注入及其循环依赖 一丶前言 前面我们分析了spring基于字段的和基于set方法注入的原理,但是没有分析第二常用的注入方式(构造器注入)(第一常用字段注入),并且在 ...

  4. spring源码学习笔记之容器的基本实现(一)

    前言 最近学习了<<Spring源码深度解析>>受益匪浅,本博客是对学习内容的一个总结.分享,方便日后自己复习或与一同学习的小伙伴一起探讨之用. 建议与源码配合使用,效果更嘉, ...

  5. Spring源码学习笔记之bean标签属性介绍及作用

    传统的Spring项目, xml 配置bean在代码中是经常遇到, 那么在配置bean的时候,这些属性的作用是什么呢? 虽然说现在boot项目兴起,基于xml配置的少了很多, 但是如果能够了解这些标签 ...

  6. Spring源码学习笔记之基于ClassPathXmlApplicationContext进行bean标签解析

    bean 标签在spring的配置文件中, 是非常重要的一个标签, 即便现在boot项目比较流行, 但是还是有必要理解bean标签的解析流程,有助于我们进行 基于注解配置, 也知道各个标签的作用,以及 ...

  7. spring源码学习之:spring容器的applicationContext启动过程

    Spring 容器像一台构造精妙的机器,我们通过配置文件向机器传达控制信息,机器就能够按照设定的模式进行工作.如果我们将Spring容器比喻为一辆汽车,可以将 BeanFactory看成汽车的发动机, ...

  8. Spring源码学习之:spring注解@Transactional

    在分析深入分析@Transactional的使用之前,我们先回顾一下事务的一些基本内容. 事务的基本概念 先来回顾一下事务的基本概念和特性.数据库事务(Database Transaction) ,是 ...

  9. Spring源码学习笔记1

    1.Spring中最核心的两个类 1)DefaultListableBeanFactory XmlBeanFactory继承自DefaultListableBeanFactory,DefaultLis ...

随机推荐

  1. ebook下载 | 灵雀云发布《 企业高管IT战略指南——为何选择容器与Kubernetes》

    发送关键词[高管指南]至灵雀云公众号,立即下载完整版电子书 "本书将提供企业领导者/IT高管应该了解的,所有关于容器技术和Kubernetes的基础认知和关键概念,突破技术语言屏障,全面梳理 ...

  2. redis如何实现数据同步

    redis如何实现数据同步 两种,1全同步,2部分同步 全备份: 在slave启动时会向master发送sync消息,master收到slave这条消息之后,将启动后台备份进程,备份完成之后,将备份数 ...

  3. NLog自定义Target之MQTT

    NLog是.Net中最流行的日志记录开源项目(之一),它灵活.免费.开源 官方支持文件.网络(Tcp.Udp).数据库.控制台等输出 社区支持Elastic.Seq等日志平台输出 实时日志需求 在工业 ...

  4. ACM-由数据范围反推算法复杂度以及算法内容

    一般ACM或者笔试题的时间限制是1秒或2秒. 在这种情况下,C++代码中的操作次数控制在 \(10^7\) 为最佳. 下面给出在不同数据范围下,代码的时间复杂度和算法该如何选择: 数据范围 算法选择 ...

  5. orcal恢复delete误删除的数据

    orcal的删除有3种:delete.truncate.drop. delete可以手动提交和回滚,且可以使用where:而truncate.drop执行即对表数据进行了修改,且不能使用where. ...

  6. WPF开发随笔收录-自定义图标控件

    一.前言 1.在以前自学的过程中,软件需要使用到图标的时候,总是第一个想法是下载一个图片来充当图标使用,但实际得出来的效果会出现模糊的现象.后来网上学习了字体图标库的用法,可以在阿里云矢量图网站那里将 ...

  7. 我用Python做了一个咖啡馆数据分析

    在做案例前,我还想回答大家一个疑问,就是excel做数据分析可以实现Python一样的效果,那用Python的意义在哪呢? 经过这段时间学习理解,我的回答是: (https://jq.qq.com/? ...

  8. 使用C#编程语言开发Windows Service服务

    转载-https://www.cnblogs.com/yubao/p/8443455.html Create Windows Service project using Visual Studio C ...

  9. poste.io自建邮件服务器

    随便说些什么 腾讯企业邮新增账号不方便,这里的主要是指不经过手机验证或微信扫码的,虽然提供了最多3个"业务邮箱",很明显不够用. EwoMail,装没装起来我不记得了,反正是不好用 ...

  10. Codeforces Round #792 (Div. 1 + Div. 2) // C ~ E

    比赛链接:Dashboard - Codeforces Round #792 (Div. 1 + Div. 2) - Codeforces C. Column Swapping 题意: 给定一个n*m ...