转自:http://www.linkedkeeper.com/detail/blog.action?bid=1045

声明式事务使用

Spring事务是我们日常工作中经常使用的一项技术,Spring提供了编程、注解、aop切面三种方式供我们使用Spring事务,其中编程式事务因为对代码入侵较大所以不被推荐使用,注解和aop切面的方式可以基于需求自行选择,我们以注解的方式为例来分析Spring事务的原理和源码实现。

首先我们简单看一下Spring事务的使用方式,配置:

<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

在需要开启事务的方法上加上@Transactional注解即可,这里需要注意的是,当tx:annotation-driven/标签在不指定transaction-manager属性的时候,会默认寻找id固定名为transactionManager的bean作为事务管理器,如果没有id为transactionManager的bean并且在使用@Transactional注解时也没有指定value(事务管理器),程序就会报错。当我们在配置两个以上的tx:annotation-driven/标签时,如下:

    <tx:annotation-driven transaction-manager="transactionManager1"/>
<bean id="transactionManager1"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource1"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager2"/>
<bean id="transactionManager2"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource2"/>
</bean>

这时第一个tx:annotation-driven/会生效,也就是当我们使用@Transactional注解时不指定事务管理器,默认使用的事务管理器是transactionManager1,后文分析源码时会具体提到这些注意点。

下面我们开始分析Spring的相关源码,首先看一下对tx:annotation-driven/标签的解析,这里需要读者对Spring自定义标签解析的过程有一定的了解,笔者后续也会出相关的文章。锁定TxNamespaceHandler:

TxNamespaceHandler

(右键可查看大图)

注册事务功能bean

这个方法比较长,关键的部分做了标记,最外围的if判断限制了tx:annotation-driven/标签只能被解析一次,所以只有第一次被解析的标签会生效。蓝色框的部分分别注册了三个BeanDefinition,分别为AnnotationTransactionAttributeSource、TransactionInterceptor和BeanFactoryTransactionAttributeSourceAdvisor,并将前两个BeanDefinition添加到第三个BeanDefinition的属性当中,这三个bean支撑了整个事务功能,后面会详细说明。我们先来看红色框的第个方法:

还记得当tx:annotation-driven/标签在不指定transaction-manager属性的时候,会默认寻找id固定名为transactionManager的bean作为事务管理器这个注意事项么,就是在这里实现的。下面我们来看红色框的第二个方法:

这两个方法的主要目的是注册InfrastructureAdvisorAutoProxyCreator,注册这个类的目的是什么呢?我们看下这个类的层次:

使用bean的后处理方法获取增强器

我们发现这个类间接实现了BeanPostProcessor接口,我们知道,Spring会保证所有bean在实例化的时候都会调用其postProcessAfterInitialization方法,我们可以使用这个方法包装和改变bean,而真正实现这个方法是在其父类AbstractAutoProxyCreator类中:

上面这个方法相信大家已经看出了它的目的,先找出所有对应Advisor的类的beanName,再通过beanFactory.getBean方法获取这些bean并返回。不知道大家还是否记得在文章开始的时候提到的三个类,其中BeanFactoryTransactionAttributeSourceAdvisor实现了Advisor接口,所以这个bean就会在此被提取出来,而另外两个bean被织入了BeanFactoryTransactionAttributeSourceAdvisor当中,所以也会一起被提取出来,下图为BeanFactoryTransactionAttributeSourceAdvisor类的层次:

Spring获取匹配的增强器

下面让我们来看Spring如何在所有候选的增强器中获取匹配的增强器:

上面的方法中提到引介增强的概念,在此做简要说明,引介增强是一种比较特殊的增强类型,它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的。通过引介增强,我们可以为目标类添加一个接口的实现,即原来目标类未实现某个接口,通过引介增强可以为目标类创建实现该接口的代理,使用方法可以参考文末的引用链接。另外这个方法用两个重载的canApply方法为目标类寻找匹配的增强器,其中第一个canApply方法会调用第二个canApply方法并将第三个参数传为false:

在上面BeanFactoryTransactionAttributeSourceAdvisor类的层次中我们看到它实现了PointcutAdvisor接口,所以会调用红框中的canApply方法进行判断,第一个参数pca.getPointcut()也就是调用BeanFactoryTransactionAttributeSourceAdvisor的getPointcut方法:

这里的transactionAttributeSource也就是我们在文章开始看到的为BeanFactoryTransactionAttributeSourceAdvisor织入的两个bean中的AnnotationTransactionAttributeSource,我们以TransactionAttributeSourcePointcut作为第一个参数继续跟踪canApply方法:

我们跟踪pc.getMethodMatcher()方法也就是TransactionAttributeSourcePointcut的getMethodMatcher方法是在它的父类中实现:

发现方法直接返回this,也就是下面methodMatcher.matches方法就是调用TransactionAttributeSourcePointcut的matches方法:

在上面我们看到其实这个tas就是AnnotationTransactionAttributeSource,这里的目的其实也就是判断我们的业务方法或者类上是否有@Transactional注解,跟踪AnnotationTransactionAttributeSource的getTransactionAttribute方法:

方法中的事务声明优先级最高,如果方法上没有声明则在类上寻找:

this.annotationParsers是在AnnotationTransactionAttributeSource类初始化的时候初始化的:

所以annotationParser.parseTransactionAnnotation就是调用SpringTransactionAnnotationParser的parseTransactionAnnotation方法:

至此,我们终于看到的Transactional注解,下面无疑就是解析注解当中声明的属性了:

Transactional注解

在这个方法中我们看到了在Transactional注解中声明的各种常用或者不常用的属性的解析,至此,事务的初始化工作算是完成了,下面开始真正的进入执行阶段。

在上文AbstractAutoProxyCreator类的wrapIfNecessary方法中,获取到目标bean匹配的增强器之后,会为bean创建代理,这部分内容我们会在Spring AOP的文章中进行详细说明,在此简要说明方便大家理解,在执行代理类的目标方法时,会调用Advisor的getAdvice获取MethodInterceptor并执行其invoke方法,而我们本文的主角BeanFactoryTransactionAttributeSourceAdvisor的getAdvice方法会返回我们在文章开始看到的为其织入的另外一个bean,也就是TransactionInterceptor,它实现了MethodInterceptor,所以我们分析其invoke方法:

这个方法很长,但是整体逻辑还是非常清晰的,首选获取事务属性,这里的getTransactionAttrubuteSource()方法的返回值同样是在文章开始我们看到的被织入到TransactionInterceptor中的AnnotationTransactionAttributeSource,在事务准备阶段已经解析过事务属性并保存到缓存中,所以这里会直接从缓存中获取,接下来获取配置的TransactionManager,也就是determineTransactionManager方法,这里如果配置没有指定transaction-manager并且也没有默认id名为transactionManager的bean,就会报错,然后是针对声明式事务和编程式事务的不同处理,创建事务信息,执行目标方法,最后根据执行结果进行回滚或提交操作,我们先分析创建事务的过程。在分析之前希望大家能先去了解一下Spring的事务传播行为,有助于理解下面的源码,这里做一个简要的介绍,更详细的信息请大家自行查阅Spring官方文档,里面有更新详细的介绍。

Spring的事务传播行为定义在Propagation这个枚举类中,一共有七种,分别为:

REQUIRED:业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务,是默认的事务传播行为。

NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。

REQUIRESNEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。

MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。

SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。

NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。

NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。

开启事务过程

判断当前线程是否存在事务就是判断记录的数据库连接是否为空并且transactionActive状态为true。

REQUIRESNEW会开启一个新事务并挂起原事务,当然开启一个新事务就需要一个新的数据库连接:

suspend挂起操作主要目的是将当前connectionHolder置为null,保存原有事务信息,以便于后续恢复原有事务,并将当前正在进行的事务信息进行重置。下面我们看Spring如何开启一个新事务:

这里我们看到了数据库连接的获取,如果是新事务需要获取新一个新的数据库连接,并为其设置了隔离级别、是否只读等属性,下面就是将事务信息记录到当前线程中:

接下来就是记录事务状态并返回事务信息:

然后就是我们目标业务方法的执行了,根据执行结果的不同做提交或回滚操作,我们先看一下回滚操作:

其中回滚条件默认为RuntimeException或Error,我们也可以自行配置。

保存点一般用于嵌入式事务,内嵌事务的回滚不会引起外部事务的回滚。下面我们来看新事务的回滚:

很简单,就是获取当前线程的数据库连接并调用其rollback方法进行回滚,使用的是底层数据库连接提供的API。最后还有一个清理和恢复挂起事务的操作:

如果事务执行前有事务挂起,那么当前事务执行结束后需要将挂起的事务恢复,挂起事务时保存了原事务信息,重置了当前事务信息,所以恢复操作就是将当前的事务信息设置为之前保存的原事务信息。到这里事务的回滚操作就结束了,下面让我们来看事务的提交操作:

在上文分析回滚流程中我们提到了如果当前事务不是独立的事务,也没有保存点,在回滚的时候只是设置一个回滚标记,由外部事务提交时统一进行整体事务的回滚。

提交操作也是很简单的调用数据库连接底层API的commit方法。

参考链接:

http://blog.163.com/asd_wll/blog/static/2103104020124801348674/

https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#spring-data-tier

Spring源码剖析9:Spring事务源码剖析的更多相关文章

  1. 结合ThreadLocal来看spring事务源码,感受下清泉般的洗涤!

    在我的博客spring事务源码解析中,提到了一个很关键的点:将connection绑定到当前线程来保证这个线程中的数据库操作用的是同一个connection.但是没有细致的讲到如何绑定,以及为什么这么 ...

  2. spring事务源码研读1

    转载摘录自:Spring事务源码分析(一)Spring事务入门 有时为了保证一些操作要么都成功,要么都失败,这就需要事务来保证. 传统的jdbc事务如下: @Test public void test ...

  3. spring事务源码解析

    前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...

  4. Spring事务源码阅读笔记

    1. 背景 本文主要介绍Spring声明式事务的实现原理及源码.对一些工作中的案例与事务源码中的参数进行总结. 2. 基本概念 2.1 基本名词解释 名词 概念 PlatformTransaction ...

  5. 【面试】足够应付面试的Spring事务源码阅读梳理(建议珍藏)

    Starting from a joke 问:把大象放冰箱里,分几步? 答:三步啊,第一.把冰箱门打开,第二.把大象放进去,第三.把冰箱门带上. 问:实现Spring事务,分几步? 答:三步啊,第一. ...

  6. 框架源码系列十一:事务管理(Spring事务管理的特点、事务概念学习、Spring事务使用学习、Spring事务管理API学习、Spring事务源码学习)

    一.Spring事务管理的特点 Spring框架为事务管理提供一套统一的抽象,带来的好处有:1. 跨不同事务API的统一的编程模型,无论你使用的是jdbc.jta.jpa.hibernate.2. 支 ...

  7. Spring系列(六):Spring事务源码解析

    一.事务概述 1.1 什么是事务 事务是一组原子性的SQL查询,或者说是一个独立的工作单元.要么全部执行,要么全部不执行. 1.2 事务的特性(ACID) ①原子性(atomicity) 一个事务必须 ...

  8. Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?

    不知道一些同学有没有这种疑问,为什么Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?那么Mybatis和Spring事务中用的Connection是同一个吗 ...

  9. SpringBoot 源码解析 (八)----- Spring Boot 精髓:事务源码解析

    本篇来讲一下SpringBoot是怎么自动开启事务的,我们先来回顾一下以前SSM中是如何使用事务的 SSM使用事务 导入JDBC依赖包 众所周知,凡是需要跟数据库打交道的,基本上都要添加jdbc的依赖 ...

  10. spring 事务源码赏析(二)

    我们在spring 事务源码赏析(一) 中分析了spring事务是如何找到目标方法,并如何将事务的逻辑织入到我们的业务逻辑中.本篇我们将会看到spring事务的核心实现: 1.事务传播机制的实现 2. ...

随机推荐

  1. 简单题[期望DP]

    也许更好的阅读体验 \(\mathcal{Description}\) 桌面上有R张红牌和B张黑牌,随机打乱顺序后放在桌面上,开始一张一张地翻牌,翻到红牌得到1美元,黑牌则付出1美元.可以随时停止翻牌 ...

  2. [leetcode] 621. Task Scheduler(medium)

    原题 思路: 按频率最大的字母来分块,频率最大的字母个数-1为分成的块数,每一块个数为n+1 比如AAABBCE,n=2, 则分为A-A- +A AAABBBCCEE,n=2,则分为AB-AB- +A ...

  3. 2019杭电多校第二场hdu6601 Keen On Everything But Triangle

    Keen On Everything But Triangle 题目传送门 解题思路 利用主席树求区间第k小,先求区间内最大的值,再求第二大,第三大--直到找到连续的三个数可以构成一个三角形.因为对于 ...

  4. C# 不同访问符的访问级别

    public----成员可以由任何代码访问. private----成员只能由类中的代码访问(如果没有使用任何关键字,就默认使用这个关键字). internal----成员只能由定义它的项目(程序集) ...

  5. python带有GIL解释器锁

    1.GIL是什么?GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定. 2.每个CPU在同一时间只能执行一个线程(在 ...

  6. sql上传木马

    第十周笔记—SQLmap和sql注入上传木马 目录 第十周笔记—SQLmap和sql注入上传木马 SQL注入 上传木马 SQLmap 六大模块 命令 参数 三种请求方式 取得系统shell sqlma ...

  7. VS Code 安装 LeetCode 插件

    练习算法绕不开的一个网站就是力扣,很多小伙伴为了拿到大厂 offer,刷题都刷到吐了. 然而如果直接在 LeetCode 上写代码,那是很痛苦的一件事,那就相当于用 txt 写代码一样,没有 IDE ...

  8. 微信小程序onLoad与onShow的区别

    现在招聘网站上,会小程序开发都可以找到月薪不错的职位了,可见小程序认可度还是可以的! 小程序声明周期onLoad与onShow的区别? onLoad页面加载时调用,可以获取参数,通过options. ...

  9. 脱壳系列_2_IAT加密壳_详细版解法1(含脚本)

    1 查看壳程序信息 使用ExeInfoPe 分析: 发现这个壳的类型没有被识别出来,Vc 6.0倒是识别出来了,Vc 6.0的特征是 入口函数先调用GetVersion() 2 用OD找OEP 拖进O ...

  10. 【iOS】UIButton 常用属性

    发现 UIButton 的相关属性不熟悉了……常用的一些属性代码如下: UIButton *add = [UIButton buttonWithType:UIButtonTypeCustom]; ad ...