Spring 源码学习笔记11——Spring事务
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。
这里会注入AutoProxyRegistrar
和ProxyTransactionManagementConfiguration
.接下来我们看些这个两个类有什么作用
1.AutoProxyRegistrar
AutoProxyRegistrar
实现了ImportBeanDefinitionRegistrar
,会向容器中注入需要的BeanDefinition
,这里最终调用了AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry)
,如果容器中没有名称为org.springframework.aop.config.internalAutoProxyCreator
的BeanDefinition那么会注入InfrastructureAdvisorAutoProxyCreator
InfrastructureAdvisorAutoProxyCreator
是AbstractAutoProxyCreator
的子类,实现了SmartInstantiationAwareBeanPostProcessor
会在getEarlyBeanReference
提前暴露代理对象或者在bean完成实例化初始化后调用postProcessAfterInitialization
对原始的bean进行aop增强,这部分我们在Spring 源码学习笔记10——Spring AOP 已经学习过了。也就是说这里会注入一个基于动态代理实现Aop增强的一个bean后置处理器
2.ProxyTransactionManagementConfiguration
注入
TransactionalEventListenerFactory
这一步是在其父类
AbstractTransactionManagementConfiguration
中完成的TransactionalEventListenerFactory
在Spring源码学习笔记5——注册BeanPostProcessor,初始化事件多播器,注册事件监听器中我们提到过,它会把标注TransactionalEventListener
的方法包装成一个ApplicationListener
来响应对应的事件。注入
TransactionInterceptor
TransactionInterceptor
是一个Advise
一个基于方法拦截器的通知,在其中实现了事务的增强逻辑。Spring 源码学习笔记10——Spring AOP 中我们分析过最后的aop代理对象执行方法的时候会调用到方法拦截器的invoke
方法,TransactionInterceptor
便是在其中实现了事务的增强。注入
BeanFactoryTransactionAttributeSourceAdvisor
BeanFactoryTransactionAttributeSourceAdvisor
是一个PointcutAdvisor
,是一个Advisor
,它使用Pointcut
来判断当前对象的方法是否需要增强,使用Advice
对方法进行增强。注入
TransactionAttributeSource
这里注入的是
AnnotationTransactionAttributeSource
,主要负责从类上,方法上,获取@Transactional
注解信息,比如抛出什么异常回滚,事务超时事件等。
看完这个类,我们基本上清楚了Spring 事务是如何实现的了,首先是通过BeanPostProcessor于Spring IOC容器结合在一起,在bean实例化初始化后调用后置处理器(如果出现循环依赖那么调用的是提前暴露对象的getEarlyBeanReference
)对bean进行Aop增强,在AbstractAutoProxyCreator
的wrapIfNecessary
方法中,会获取全部的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
RuleBasedTransactionAttribute
对rollbackOn
进行了扩展,配合SpringTransactionAnnotationParser
会解析事务注解中的rollbackFor
,rollbackForClassName
,noRollbackFor
,noRollbackForClassName
属性来判断异常抛出时是否需要回滚事务。如果这个属性没有值的话,会调用父类的rollbackOn
,最终只会在RuntimeException
和 Error
上面回滚。
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
封装事务的处理配置信息并绑定到当前线程
其中getTransaction
在AbstractPlatformTransactionManager
中是一个final
的模板方法,它首先判断是否存在一个事务,如果存在那么调用handleExistingTransaction
来创建事务,如果不存在那么根据事务传播级别来决定是否创建事务。
不存在事务的情况
首先要求超时时长不能小于-1.-1表示的
使用底层事务系统的默认超时,如果不支持超时,则使用无
然后如果的传播级别是
MANDATORY
支持当前事务,如果不存在则抛出异常,也就是说MANDATORY
要求外层调用方法是在一个具备事务的情况下进行的调用.如果隔离级别是
Required(支持当前事务,如果当前不存在事务那么创建一个新事务)
,RequireNew(创建一个新事务,如果存在事务则暂停当前事务)
,Nested(如果当前事务存在,则在嵌套事务中执行)
那么会执行下面的逻辑首先是挂起当前事务,由于当前不存在事务,其实是把当前存在
TransactionSynchronization
事务同步回调的接口信息保存在SuspendedResourcesHolder
中。在DataSourceTransactionManager
中doBegin
方法会获取对应的Connection,然后根据事务定义对Connection进行设置,比如如果是只读事务那么会执行SET TRANSACTION READ ONLY
设置事务只读,设置超时时长,关闭自动提交等,并且把创建的事务信息绑定到resources ThreadLocal
中。prepareSynchronization
负责把事务相关信息设置到ThreadLocal中,并且初始化TransactionSynchronization
的LinkedHashSet
,这样我们我们通过TransactionSynchronizationManager
加入一些回调方法的时候不会抛NPE,之所以使用LinkedHashSet
是TransactionSynchronizationManager
支持Ordered接口
,@Order注解
进行排序如果是
SUPPORTS(支持当前事务,如果不存在,则以非事务方式执行)
,NOT_SUPPORTED(不支持当前事务;始终以非事务方式执行)
,NEVER(不支持当前事务,如果当前事务存在,则抛出异常)
那么不会开启事务,但是支持事务同步
存在事务的情况
是如何判断是否存在事务的
首先
doGetTransaction
方法会获取resources ThreadLocal
中的事务信息,这个事务信息是在doBegin
方法中绑定的然后调用
isExistingTransaction
来判断是否存在事务,同样
doBegin
方法会设置transactionActive
为true。
最后如果存在事务那么会执行下面的handleExistingTransaction
来根据事务传播级别来创建事务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
调用完业务方法后,会调用cleanupTransactionInfo
把oldTransactionInfo
重写设置到ThreadLocal中,这意味着B方法执行结束,回到了A方法的调用栈中。
2.5 回调业务逻辑InvocationCallback#proceedWithInvocation
其实调用的是MethodInvocation#proceed
,如果当前对象存在多层代理,比如先事务代理,再基于@AspectJ
注解的方法调用时长统计,那么后面代理增强也会执行,具体逻辑在ReflectiveMethodInvocation#proceed
方法中,如果还存在其他的拦截器链那么会继续执行拦截器中的逻辑,否则直接执行我们自己的业务逻辑代码,这部分在Spring 源码学习笔记10——Spring AOP 说到过。
2.6 completeTransactionAfterThrowing 业务逻辑出现异常时的处理
这里rollbackOn
取决于事务注解上面标注的在什么异常上回滚,在什么异常上不回滚,默认是在RuntimeException
和Error
上面才会回滚。
下面我们看下是如何回滚事务的
执行回滚DataSourceTransactionManager
是调用的Connection#rollback
方法,这里首先会获取ThreadLocal中的TransactionSynchronization
并且按照@Order
和Ordered
排序然后依次调用beforeCompletion
,如果具备回滚点,那么直接回滚到保存点,如果是一个新事务,那么直接回滚,如果不是一个独立的事务,只是标记需要回滚,执行完这些后,还会回调triggerAfterCompletion
,排序然后调用TransactionSynchronization#afterCompletion
方法,然后调用cleanupAfterCompletion
清理资源,并且恢复被挂起的线程。
2.7 commitTransactionAfterReturning提交事务
具体逻辑在processCommit
中进行
DataSourceTransactionManager
提交事务是执行的是Connection#commit
同样完成只会还会调用triggerAfterCommit
,triggerAfterCompletion
并清理ThreadLocal中的内容,并且恢复被挂起的事务。
3.事务同步回调接口TransactionSynchronization
其中beforeCommit
,beforeCompletion
,aterCommit
,afterCompletion
,只有当前事务时一个独立事务的时候才会回调,而且如果提交或者回滚的时候出现异常,beforeCompletion
,afterCompletion
也会被调用,我们可以通过TransactionSynchronizationManager#registerSynchronization
注册回调的逻辑。
那么什么时候事务被视作时一个独立事务昵
- 如果外部不存在一个事务,并且传播级别是
REQUIRED,REQUIRES_NEW,NESTED
- 如果外部存在一个事务,且传播级别为
REQUIRES_NEW
- 如果外部存在一个事务,且传播级别为嵌套事务,但是此时不是通过保存点来实现嵌套事务
Spring 源码学习笔记11——Spring事务的更多相关文章
- Spring 源码学习笔记10——Spring AOP
Spring 源码学习笔记10--Spring AOP 参考书籍<Spring技术内幕>Spring AOP的实现章节 书有点老,但是里面一些概念还是总结比较到位 源码基于Spring-a ...
- Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点
Spring源码学习笔记12--总结篇,IOC,Bean的生命周期,三大扩展点 参考了Spring 官网文档 https://docs.spring.io/spring-framework/docs/ ...
- Spring源码学习笔记9——构造器注入及其循环依赖
Spring源码学习笔记9--构造器注入及其循环依赖 一丶前言 前面我们分析了spring基于字段的和基于set方法注入的原理,但是没有分析第二常用的注入方式(构造器注入)(第一常用字段注入),并且在 ...
- spring源码学习笔记之容器的基本实现(一)
前言 最近学习了<<Spring源码深度解析>>受益匪浅,本博客是对学习内容的一个总结.分享,方便日后自己复习或与一同学习的小伙伴一起探讨之用. 建议与源码配合使用,效果更嘉, ...
- Spring源码学习笔记之bean标签属性介绍及作用
传统的Spring项目, xml 配置bean在代码中是经常遇到, 那么在配置bean的时候,这些属性的作用是什么呢? 虽然说现在boot项目兴起,基于xml配置的少了很多, 但是如果能够了解这些标签 ...
- Spring源码学习笔记之基于ClassPathXmlApplicationContext进行bean标签解析
bean 标签在spring的配置文件中, 是非常重要的一个标签, 即便现在boot项目比较流行, 但是还是有必要理解bean标签的解析流程,有助于我们进行 基于注解配置, 也知道各个标签的作用,以及 ...
- spring源码学习之:spring容器的applicationContext启动过程
Spring 容器像一台构造精妙的机器,我们通过配置文件向机器传达控制信息,机器就能够按照设定的模式进行工作.如果我们将Spring容器比喻为一辆汽车,可以将 BeanFactory看成汽车的发动机, ...
- Spring源码学习之:spring注解@Transactional
在分析深入分析@Transactional的使用之前,我们先回顾一下事务的一些基本内容. 事务的基本概念 先来回顾一下事务的基本概念和特性.数据库事务(Database Transaction) ,是 ...
- Spring源码学习笔记1
1.Spring中最核心的两个类 1)DefaultListableBeanFactory XmlBeanFactory继承自DefaultListableBeanFactory,DefaultLis ...
随机推荐
- ESXI系列问题整理以及记录——使用Windows PowerShell中的SSH功能连接ESXI控制台
首先进入ESXI管理页面,开启ESXI的SSH功能 接下来到位于同一局域网的Win主机上开启Powershell,如果ESXI主机的IP地址为192.168.1.77,则在Powershell中输入: ...
- 实现领域驱动设计 - 使用ABP框架 - 存储库
存储库 Repository 是一个类似于集合的接口,领域层和应用程序层使用它来访问数据持久性系统(数据库),以读写业务对象(通常是聚合) 常见的存储库原则是: 在领域层定义一个存储库接口(因为它被用 ...
- canvas简易画布
今天学习了canvas,利用它做了一个简易版的画板,校验自己所学的知识,分享出来以供大家学习指教.先上效果图. 主要是使用了canvas的stroke和clearReact来实现画板的绘画和橡皮擦功能 ...
- SAP Grid control( ALV Grid 列表 自定义 按钮)
ALV 列表和按钮 效果 源代码 PROGRAM bcalvc_tb_menu_with_def_but. *&&&&&&&&& ...
- bat-Office激活命令
激活命令 cd C:\Program Files\Microsoft Office\Office16 //然后目录对的话,该目录下面应该有个 OSPP.VBS cscript ospp.vbs /ds ...
- docker 映射端口穿透内置防火墙
一.问题现象 1.现象举例: # 自制的springboot项目的dockerfile # springboot 其实就是一个简单的hello-world程序,写了一个HelloController ...
- 在linux上配置Maven环境变量
1.首先下载maven ,这里我使用的是3.8.1 Maven – Download Apache Maven 2.在linux环境中,将maven上传至 /usr/local/目录中 这里我将mav ...
- 利用MySQL中的乐观锁和悲观锁实现分布式锁
背景 对于一些并发量不是很高的场景,使用MySQL的乐观锁实现会比较精简且巧妙. 下面就一个小例子,针对不加锁.乐观锁以及悲观锁这三种方式来实现. 主要是一个用户表,它有一个年龄的字段,然后并发地对其 ...
- Collections集合工具类的方法_sort(List)和sort(List,Comparator)方法
Comparator比较器 我们还是先研究这个方法 public static <T> void sort(List<T> list):将集合中元素按照默认规则排序. 不过这次 ...
- docker for window WSL 2 installation is incomplete 错误,导致docker无法启动
1.错误截图如下: 2.错误原因:由于wsl2版本旧,根据提示让我们手动更新包,去微软官网下载最新wsl2后,安装完成重启即可解决. 3.下载地址:download地址