目录
- 特别声明
- 前置知识
- @Transactional简单介绍
- *Spring事务传播类型(非常重要)
- @Transactional使用示例分析
- 示例一:常用
- 示例二:发生异常回滚
- 示例三:try-catch【经典】
- 示例四:REQUIRES_NEW,没有捕获异常
- 课程内容
- 一、Spring之事务
- 1.1 简单回顾
- 1.2 概念回顾
- 1.3 核心方法讲解
- 二、方法讲解
- 2.1 整体流程图
- 2.2 从@EnableTransactionManagement讲起:源码入口
- 2.3 @EnableTransactionManagement导入的类
- 2.4 ProxyTransactionManagementConfiguration.class:注册了Spring事务的Advisor
- 2.5 TransactionInterceptor#invoke():Spring事务的Advice,核心处理方法
- 2.6 TransactionAttributeSource#getTransactionAttribute:获取注解上的属性
- 2.7 TransactionInterceptor#determineTransactionManager:处理transactionManager和value属性
- 2.8 TransactionInterceptor#createTransactionIfNecessary:开始创建事务【入口】
- 2.9 PlatformTransactionManager#getTransaction:开启事务【真正干活的地方】
- 2.10 TransactionInterceptor#completeTransactionAfterThrowing:回滚
- 2.11 TransactionInterceptor#commitTransactionAfterReturning:提交
- 2.12 回滚跟提交补充
- 2.13 结束
- 学习总结
特别声明
事务是具有原子性的,同一个事务之下的所有操作是一个整体,要么一起回滚,要么一起提交!
前置知识
@Transactional简单介绍
@Transactional 注解相信大家并不陌生,平时开发中很常用的一个注解。它可以作用在类上面,所有该类的public方法都配置相同的事务属性信息;也可以作用在方法上面,表示方法被纳入了事务管理钟。被事务管理的方法,能保证方法内多个数据库操作要么同时成功、要么同时失败。但是使用@Transactional注解时又需要注意一些细节,不然一个不小心就事务失效了。
下面@Transactional
注解的源码,我们来看看它都有什么属性:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {/*** 同下面的transactionManager,用来确定目标的事务管理器的名称*/@AliasFor("transactionManager")String value() default "";/*** 同上面的value,用来确定目标的事务管理器的名称*/String transactionManager() default "";/*** 事务传播类型/策略。默认值为 Propagation.REQUIRED*/Propagation propagation() default Propagation.REQUIRED;/*** 事务的隔离级别,默认值为 Isolation.DEFAULT*/Isolation isolation() default Isolation.DEFAULT;/*** 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。*/int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;/*** 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。*/boolean readOnly() default false;/*** 定义零(0)个或多个异常类,这些异常类必须是Throwable的子类,指示哪些异常类型必须引起事务回滚。* 默认情况下,事务将在RuntimeException和Error上回滚,但不会在检查异常(业务异常)上回滚.* 这是构造回滚规则的首选方法(与rollbackForClassName相反),匹配异常类型、它的子类和它的嵌套类。*/Class<? extends Throwable>[] rollbackFor() default {};/*** 定义零(0)个或多个异常类,这些异常类必须是Throwable的子类,指示哪些异常类型不能导致事务回滚。* 这是构造回滚规则的首选方法(与noRollbackForClassName相反),匹配异常类型、它的子类和它的嵌套类。*/Class<? extends Throwable>[] noRollbackFor() default {};
}
上面贴的基本上是我们在业务代码中能使用到的关于@Transactional
注解的了。通过属性我们可以看出,@Transactional
注解具备以下作用:
- 指定目标事务管理器
- 指定事务的传播类型
- 指定事务的隔离级别
- 指定事务的超时时间
- 指定事务是否为只读事务
- 指示哪些异常类型能导致事务回滚
- 指示哪些异常类型不能导致事务回滚
看完之后是不是觉得内心毫无波澜,甚至有点不知所以然?诶,我为什么要把这些属性罗列出来呢?并且还说了一堆废话。
O,我的兄弟们呐,既然@Transactional
注解上给出了这些属性并且支持我们修改他们的值来改变注解的行为策略,那它在源码里面肯定要支持的呀。我知道不少人看完我这句话会有这种想法:这…,听君一席话入听一席话呀!这个我也知道啊,还要你说吗。那我只能说:如果你已经悟了这一点,说明你不是我的目标提醒群体。
我相信,肯定也有不少朋友一脸懵逼的进来,最后也没抓住这个源码解析的重点。知道了上述属性的作用,对我们阅读源码其实是有大大的帮助的。还是那句话:通过业务阅读源码,远比通过源码洞悉业务简单得多!
好啦,言归正传。在上面的属性中,其中尤为重要的,当是propagation
属性,事务的传播类型/策略。默认为Propagation.REQUIRED
,另外还有一些其他属性,分别表现为不一样的策略。下面我们再进一步简单介绍下
*Spring事务传播类型(非常重要)
- *Propagation.REQUIRED:直译:必须的。默认的策略。表示若当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务
- Propagation.SUPPORTS:直译:支持的。表示若当前存在事务,则加入该事务;如果当前不存在事务,则以【非事务】的方式继续运行
- Propagation.MANDATORY:直译:强制的(强制需要事务)。表示若当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常
- *Propagation.REQUIRES_NEW:直译:新建的。重新创建一个新的事务,如果当前存在事务,暂停当前的事务
- Propagation.NOT_SUPPORTED:直译:被支持的。以非事务的方式运行,如果当前存在事务,暂停当前的事务
- Propagation.NEVER:直译:从来不。以非事务的方式运行,如果当前存在事务,则抛出异常
- Propagation.NESTED:直译:嵌套的。它整体上和 Propagation.REQUIRED 效果一样,但是当出现异常的时候不一样。
NESTED
会在执行方法前设置一个safePoint
安全点,当出现回滚的时候,会回滚到当前安全点,而不是整体回滚
重要的事情说三遍
重要的事情说三遍
重要的事情说三遍
(PS:我希望大家看了上面的概念之后,不要过过脑门就算了。起码你得知道,哪些类型,会在同一个事务下进行,哪些会新建一个事务运行。并且标红的两个一定要背下来,重中之重)
@Transactional使用示例分析
示例一:常用
源码如下:
@Component
public class TransUserService {@Resourceprivate JdbcTemplate template;@Resourceprivate TransUserService userService;@Transactionalpublic void test() {String sql = "INSERT INTO shen_name values('zhang', 'san');";template.execute(sql);userService.a();}@Transactionalpublic void a() {String sql = "INSERT INTO shen_name values('li', 'si');";template.execute(sql);}
}
事务分析:默认情况下传播机制为REQUIRED,表示若当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。所以上面这种情况的执行流程如下:
- 新建一个数据库连接conn,默认的事务传播策略,开启事务
- 设置conn的autocommit为false
- 执行test方法中的sql,插入一条记录
- 进入a方法,默认的事务传播策略,加入当前事务,执行a方法中的sql,插入一条记录
- 执行conn的commit()方法进行提交
结果如下:
PS:大家有没有注意到上面的一个细节?TransUserService
类下面,有一个属性TransUserService userService
,自己依赖自己。然后再test
方法里面,这样来调用userService.a();
方法。为嘛啊?因为啊,当方法加上@Transactional
注解之后,这个类就被代理了。如果此时,我们在test()
中这样调用:
@Transactional
public void test() {// test方法中的sql:insert......this.a();
}
那仅仅是普通的Java方法a()
而已,他是不会去处理你a()
上面的@Transactional
的。不知道大家理解不?但是通过自己依赖自己,Spring就会帮我们找到自己的代理对象,等于我还是用的代理对象去调用方法a()
了。
示例二:发生异常回滚
假如是这种情况:
@Component
public class TransUserService {@Resourceprivate JdbcTemplate template;@Resourceprivate TransUserService userService;@Transactionalpublic void test() {String sql = "INSERT INTO shen_name values('zhang', 'san');";template.execute(sql);userService.a();int result = 100/0;}@Transactionalpublic void a() {String sql = "INSERT INTO shen_name values('li', 'si');";template.execute(sql);}
}
又或者是这种情况:
@Component
public class TransUserService {@Resourceprivate JdbcTemplate template;@Resourceprivate TransUserService userService;@Transactionalpublic void test() {String sql = "INSERT INTO shen_name values('zhang', 'san');";template.execute(sql);userService.a();}@Transactionalpublic void a() {String sql = "INSERT INTO shen_name values('li', 'si');";template.execute(sql);int result = 100/0;}
}
事务分析:所以上面这种情况的执行流程如下:
- 新建一个数据库连接conn,默认的事务传播策略,开启事务
- 设置conn的autocommit为false
- 执行test方法中的sql,插入一条记录
- 进入a方法,默认的事务传播策略,加入当前事务,执行a方法中的sql,插入一条记录
- 发现异常
- 执行conn的rollback()方法进行回滚,由于事务的原子性,所以两个方法中的sql都会回滚掉
结果如下:
示例三:try-catch【经典】
那再假如是这种情况呢,try-catch
住:
@Component
public class TransUserService {@Resourceprivate JdbcTemplate template;@Resourceprivate TransUserService userService;@Transactionalpublic void test() {String sql = "INSERT INTO shen_name values('zhang', 'san');";template.execute(sql);try {userService.a();} catch (Exception e) {System.out.println("发生错误,捕获异常");}}@Transactionalpublic void a() {String sql = "INSERT INTO shen_name values('li', 'si');";template.execute(sql);int result = 100/0;}
}
会不会有同学觉得是test
里面的执行成功,a()
的插入失败?如果你是这么想的,那你肯定没完全理解透我在【特别声明】里说到的内容。事务是具有原子性的,同一个事务之下的所有操作是一个整体,要么一起回滚,要么一起提交!
事务分析:所以上面这种情况的执行流程如下:
- 新建一个数据库连接conn,默认的事务传播策略,开启事务
- 设置conn的autocommit为false
- 执行test方法中的sql,插入一条记录
- 进入a方法,默认的事务传播策略,加入当前事务,执行a方法中的sql,插入一条记录
- a方法抛出异常,需要回滚
- 退回到test方法,捕捉了异常,然而并没有卵用
- 执行conn的rollback()方法进行回滚,由于事务的原子性,所以两个方法中的sql都会回滚掉
结果如下:
示例四:REQUIRES_NEW,没有捕获异常
如果是这种情况:
@Component
public class UserService {@Autowiredprivate UserService userService;@Transactionalpublic void test() {// test方法中的sqluserService.a();}@Transactional(propagation = Propagation.REQUIRES_NEW)public void a() {// a方法中的sqlint result = 100/0;}
}
事务分析:所以上面这种情况的执行流程如下:
- 新建一个数据库连接conn,默认的事务传播策略,开启事务test
- 设置conn的autocommit为false
- 执行test方法中的sql
- 进入a方法,事务策略为REQUIRES_NEW,需要新建一个数据库连接conn2,开启一个新的事务a
- 执行a方法中的sql
- 抛出异常,事务a需要回滚
- 执行conn2的rollback()方法进行回滚
- 由于调用方法
a()
没有捕获异常,所以在这里会继续抛出异常给到方法test
,紧接着回滚 - 执行conn的rollback()方法进行回滚,最终还是两个方法中的sql都回滚了
课程内容
一、Spring之事务
1.1 简单回顾
我们在前置知识里面简单的给大家演示了几个我们在工作中比较常用的@Tranactional场景,并且也简单的做了事务流程分析。但显然,Spring事务也不会仅仅只是做了这些而已。那么,更具体、更详细的过程是怎样的呢?Spring事务传播机制又是如何在代码中体现的呢?
在开发过程中,经常会出现一个方法调用另外一个方法,那么这里就涉及到了多种场景,比如a()
调用b()
,那么我们可能需要考虑如下情况:
a()
和b()
方法中的所有sql需要在同一个事务中吗?a()
和b()
方法需要单独的事务吗?a()
需要在事务中执行,b()
还需要在事务中执行吗?- 等等情况…
所以,这就要求Spring事务能支持上面各种场景,这就是Spring事务传播机制的由来。那Spring事务传播机制是如何实现的呢?
我们来看看上述场景中第二种情况,a()
在一个事务中执行,调用b()
方法时需要新开一个事务执行时的,分别的场景是怎样的:
- 首先,代理对象执行
a()
方法前,先利用事务管理器新建一个数据库连接a - 将数据库连接a的
autocommit
改为false - 把数据库连接a设置到
ThreadLocal
中(这一步没有经验的朋友也许没想到,或者比较陌生。简单来说,就是为了保证当前线程内所有的方法用的数据库连接是同一个。如果连接都不一样,那事务如何生效?) - 执行
a()
方法中的sql - 执行
a()
方法过程中,调用了b()
方法(注意用代理对象调用b()方法)- 代理对象执行
b()
方法前,判断出来了当前线程中已经存在一个数据库连接a了(用上面的ThreadLocal
可以判断出来),表示当前线程其实已经拥有一个Spring事务了,则进行挂起 - 挂起就是把
ThreadLocal
中的数据库连接a从ThreadLocal
中移除,并放入一个挂起资源对象中(下面会解释) - 挂起完成后,再次利用事务管理器新建一个数据库连接b
- 将数据库连接b的
autocommit
改为false - 把数据库连接b设置到
ThreadLocal
中 - 执行b()方法中的sql
b()
方法正常执行完,则从ThreadLocal
中拿到数据库连接b进行提交- 提交之后会恢复所挂起的数据库连接a,这里的恢复,其实只是把在挂起资源对象中所保存的数据库连接a再次设置到
ThreadLocal
中
- 代理对象执行
a()
方法正常执行完,则从ThreadLocal
中拿到数据库连接a进行提交
上面简单描述了开启事务的,以及在Propagation.REQUIRES_NEW
事务传播类型下,单独新开事务的过程。这算是一个比较典型的事务过程了,我们也会在这个基础上,大概的讲述一下源码流程。
1.2 概念回顾
其实在Spring事务的研究当中,涉及到的最重要的概念还是AOP里面的Advisor
,Advice
以及PointCut
。当然,还有在前置知识里面提到的【Spring事务传播类型】。至于跟Spring Bean生命周期相关的东西反而不算很多。就算有也在前面的AOP内容中讲过了。
1.3 核心方法讲解
由于关键代码还算是比较多,并且阅读性不算很高,所以这边会按照这样的方式讲解:
- 首先,我会先给出流程图,并且按照树形结构的方式写
- 其次,我将按照源码调用次序,【自上而下、从左往右】的方式映射到树形结构上
- 最后到了这里,我感觉大家需要打开Spring源码,一边看文章,一边看源码的方式来阅读了
- 我并不会讲解所有的源码,只会将一些比较陌生、或者关键的源码点一下。另外,有一些细枝末节确实没必要仔细研究。因为不了解它们对我们整体阅读源码没有任何影响
二、方法讲解
2.1 整体流程图
由于关键代码还算是比较多,并且阅读性不算很高,所以这边会按照这样的方式讲解:
- 首先,我会先给出流程图,并且按照树形结构的方式写
- 其次,我将按照源码调用次序,【自上而下、从左往右】的方式映射到树形结构上
- 最后到了这里,我感觉大家需要打开Spring源码,一边看文章,一边看源码的方式来阅读了
- 我并不会讲解所有的源码,只会将一些比较陌生、或者关键的源码点一下。另外,有一些细枝末节确实没必要仔细研究。因为不了解它们对我们整体阅读源码没有任何影响
流程图如下:
2.2 从@EnableTransactionManagement讲起:源码入口
在Spring中,如果我们想要开启事务的话,需要使用这个注解,就好比是使用AOP需要使用@EnableAspectJAutoProxy一样。这个开启Spring事务注解,本质上就是增加了一个Advisor,该注解代理的功能就是向Spring容器中添加了两个Bean:AutoProxyRegistrar
和ProxyTransactionManagementConfiguration
。
AutoProxyRegistrar
:AutoProxyRegistrar主要的作用是向Spring容器中注册了一个InfrastructureAdvisorAutoProxyCreator
的Bean。而InfrastructureAdvisorAutoProxyCreator
继承了AbstractAdvisorAutoProxyCreator
,所以这个类的主要作用就是开启自动代理的作用,也就是一个BeanPostProcessor,会在初始化后步骤中去寻找解析Advisor类型的Bean,并判断当前某个Bean是否有匹配的Advisor,是否需要利用动态代理产生一个代理对象。- ProxyTransactionManagementConfiguration是一个配置类,它又定义并且向Spring容器注册了另外三个bean:
BeanFactoryTransactionAttributeSourceAdvisor
:它是一个Advisor。所以里面会包含匹配策略PointCut,以及真正执行的切入方法通知AdviceAnnotationTransactionAttributeSource
:相当于BeanFactoryTransactionAttributeSourceAdvisor中的Pointcut。其就是用来判断某个类上是否存在@Transactional注解,或者判断某个方法上是否存在@Transactional注解的(其实仔细想想就很容易明白。我们正常使用的pointcut是一段AspectJ匹配表达式,但在事务里面没那么复杂,某个类或方法上面有@Transactional注解就是我们要找的目标了)TransactionInterceptor
:相当于BeanFactoryTransactionAttributeSourceAdvisor中的Advice。它是一个MethodInterceptor的子类(我们在前面的AOP讲过,其实所有的Advice都是,或者说可以是MethodInterceptor。在解析的过程中会把所有的Advice封装成MethodInterceptor的),当某个类中存在@Transactional注解时,到时就产生一个代理对象作为Bean,代理对象在执行某个方法时,最终就会进入到TransactionInterceptor的invoke()方法。
O,如果大家了解AOP或者看过我前面的笔记的话,对上面提到的这三个概念并不会感到很陌生,并且会很轻易地知道,我们要研究的类无非就两个:
AnnotationTransactionAttributeSource
是如何扮演pointCut
的角色,然后使用何种策略匹配切点的TransactionInterceptor
又是如何实现代理加强方法的呢?很显然,我们在上面【简单回顾】提到的流程,都将会在TransactionInterceptor
中完成!
既然知道了这两个类是我们核心研究类,那大家应该还记得它们分别会在Bean的哪个生命周期作用吗?这个问题就抛给大家了。
2.3 @EnableTransactionManagement导入的类
其实上面已经讲过大概的作用了,这边再次立在这里,只是单纯的提醒大家不要忘了有这个步骤而已。它们是:AutoProxyRegistrar.class
、ProxyTransactionManagementConfiguration.class
2.4 ProxyTransactionManagementConfiguration.class:注册了Spring事务的Advisor
对应流程图的:
源码如下:
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {// 注册了一个Advisor@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();advisor.setTransactionAttributeSource(transactionAttributeSource);advisor.setAdvice(transactionInterceptor);if (this.enableTx != null) {advisor.setOrder(this.enableTx.<Integer>getNumber("order"));}return advisor;}// 注册了一个PointCut依赖属性。原则来说这个并不是真正意义的PointCut// 但是PointCut依赖这个类实现功能,所以,我们把它当作PointCut@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public TransactionAttributeSource transactionAttributeSource() {return new AnnotationTransactionAttributeSource();}// 注册了一个Advice,MethodInterceptor的子类@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {TransactionInterceptor interceptor = new TransactionInterceptor();interceptor.setTransactionAttributeSource(transactionAttributeSource);if (this.txManager != null) {interceptor.setTransactionManager(this.txManager);}return interceptor;}}
方法解读:上面这段代码向Spring中注册了3个非常重要的Bean
2.5 TransactionInterceptor#invoke():Spring事务的Advice,核心处理方法
对应流程图的:
源码如下:
@Override@Nullablepublic Object invoke(MethodInvocation invocation) throws Throwable {Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// Adapt to TransactionAspectSupport's invokeWithinTransaction...return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {@Override@Nullablepublic Object proceedWithInvocation() throws Throwable {return invocation.proceed();}@Overridepublic Object getTarget() {return invocation.getThis();}@Overridepublic Object[] getArguments() {return invocation.getArguments();}});}
方法解读:我们在最前面的【前置知识·示例合集】以及【简单回顾】里面大概分析了一下,方法存在事务注解的时候,存在注解的方法相互调用的时候,Spring事务底层干了什么事情,就是在这个方法体内实现的!
2.6 TransactionAttributeSource#getTransactionAttribute:获取注解上的属性
对应流程图:
这个其实并没有很难,但是为了让大家看的清晰一点,我就不放当前方法的源码了,而是直接给大家看看,最终这个方法调用的,解析注解的关键源码。如下:
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();Propagation propagation = attributes.getEnum("propagation");rbta.setPropagationBehavior(propagation.value());Isolation isolation = attributes.getEnum("isolation");rbta.setIsolationLevel(isolation.value());rbta.setTimeout(attributes.getNumber("timeout").intValue());String timeoutString = attributes.getString("timeoutString");Assert.isTrue(!StringUtils.hasText(timeoutString) || rbta.getTimeout() < 0,"Specify 'timeout' or 'timeoutString', not both");rbta.setTimeoutString(timeoutString);rbta.setReadOnly(attributes.getBoolean("readOnly"));rbta.setQualifier(attributes.getString("value"));rbta.setLabels(Arrays.asList(attributes.getStringArray("label")));List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {rollbackRules.add(new RollbackRuleAttribute(rbRule));}for (String rbRule : attributes.getStringArray("rollbackForClassName")) {rollbackRules.add(new RollbackRuleAttribute(rbRule));}for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {rollbackRules.add(new NoRollbackRuleAttribute(rbRule));}for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {rollbackRules.add(new NoRollbackRuleAttribute(rbRule));}rbta.setRollbackRules(rollbackRules);return rbta;}
最后封装成了RuleBasedTransactionAttribute
对象,并且缓存了起来。
2.7 TransactionInterceptor#determineTransactionManager:处理transactionManager和value属性
对应流程图:
源码如下:
@Nullable
protected TransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {// Do not attempt to lookup tx manager if no tx attributes are setif (txAttr == null || this.beanFactory == null) {return getTransactionManager();}String qualifier = txAttr.getQualifier();if (StringUtils.hasText(qualifier)) {return determineQualifiedTransactionManager(this.beanFactory, qualifier);}else if (StringUtils.hasText(this.transactionManagerBeanName)) {return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);}else {TransactionManager defaultTransactionManager = getTransactionManager();if (defaultTransactionManager == null) {defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);if (defaultTransactionManager == null) {defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);this.transactionManagerCache.putIfAbsent(DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);}}return defaultTransactionManager;}
}
方法解读:这里就是处理transactionManager
和value
属性逻辑的地方。估计有朋友会说:没看到处理value
属性啊,只有transactionManager
。同学啊,你是不是忘了别名啊?回到梦开始的地方再仔细瞧瞧
2.8 TransactionInterceptor#createTransactionIfNecessary:开始创建事务【入口】
对应流程图:
这里就不贴这个源码了,毕竟这里是入口而已,而且干的事情也告诉大家了。
2.9 PlatformTransactionManager#getTransaction:开启事务【真正干活的地方】
对应流程图:
源码如下:
@Overridepublic final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException {// Use defaults if no transaction definition given.TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());Object transaction = doGetTransaction();boolean debugEnabled = logger.isDebugEnabled();if (isExistingTransaction(transaction)) {// Existing transaction found -> check propagation behavior to find out how to behave.return handleExistingTransaction(def, transaction, debugEnabled);}// Check definition settings for new transaction.if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());}// No existing transaction found -> check propagation behavior to find out how to proceed.if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");}else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {SuspendedResourcesHolder suspendedResources = suspend(null);if (debugEnabled) {logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);}try {return startTransaction(def, transaction, debugEnabled, suspendedResources);}catch (RuntimeException | Error ex) {resume(null, suspendedResources);throw ex;}}else {// Create "empty" transaction: no actual transaction, but potentially synchronization.if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {logger.warn("Custom isolation level specified but no actual transaction initiated; " +"isolation level will effectively be ignored: " + def);}boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);}}
方法解读:如流程图所示,事务的【传播类型】就在此被用到了。根据不同的【传播类型】,以及当前线程是否已经存在事务了isExistingTransaction()
,都会决定后续的源码流程怎么走。再往里面走的新建事务的源码startTransaction()
,其最终调的也还是doBegin()
,这里就只贴出对应的doBegin()
方法吧。如下:
protected void doBegin(Object transaction, TransactionDefinition definition) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;Connection con = null;try {if (!txObject.hasConnectionHolder() ||txObject.getConnectionHolder().isSynchronizedWithTransaction()) {Connection newCon = obtainDataSource().getConnection();if (logger.isDebugEnabled()) {logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");}txObject.setConnectionHolder(new ConnectionHolder(newCon), true);}txObject.getConnectionHolder().setSynchronizedWithTransaction(true);con = txObject.getConnectionHolder().getConnection();Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);txObject.setPreviousIsolationLevel(previousIsolationLevel);txObject.setReadOnly(definition.isReadOnly());// 设置autoCommitif (con.getAutoCommit()) {txObject.setMustRestoreAutoCommit(true);if (logger.isDebugEnabled()) {logger.debug("Switching JDBC Connection [" + con + "] to manual commit");}con.setAutoCommit(false);}prepareTransactionalConnection(con, definition);txObject.getConnectionHolder().setTransactionActive(true);int timeout = determineTimeout(definition);if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {txObject.getConnectionHolder().setTimeoutInSeconds(timeout);}// Bind the connection holder to the thread.if (txObject.isNewConnectionHolder()) {TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());}}catch (Throwable ex) {if (txObject.isNewConnectionHolder()) {DataSourceUtils.releaseConnection(con, obtainDataSource());txObject.setConnectionHolder(null, false);}throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);}}
2.10 TransactionInterceptor#completeTransactionAfterThrowing:回滚
对应流程图:
当前方法只是入口而已,阵阵回滚的方法是processRollback()
,所以这边贴processRollback
的源码,如下:
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {try {boolean unexpectedRollback = unexpected;try {triggerBeforeCompletion(status);if (status.hasSavepoint()) {if (status.isDebug()) {logger.debug("Rolling back transaction to savepoint");}status.rollbackToHeldSavepoint();}else if (status.isNewTransaction()) {if (status.isDebug()) {logger.debug("Initiating transaction rollback");}doRollback(status);}else {// Participating in larger transactionif (status.hasTransaction()) {if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {if (status.isDebug()) {logger.debug("Participating transaction failed - marking existing transaction as rollback-only");}doSetRollbackOnly(status);}else {if (status.isDebug()) {logger.debug("Participating transaction failed - letting transaction originator decide on rollback");}}}else {logger.debug("Should roll back transaction but cannot - no transaction available");}// Unexpected rollback only matters here if we're asked to fail earlyif (!isFailEarlyOnGlobalRollbackOnly()) {unexpectedRollback = false;}}}catch (RuntimeException | Error ex) {triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);throw ex;}triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);// Raise UnexpectedRollbackException if we had a global rollback-only markerif (unexpectedRollback) {throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");}}finally {cleanupAfterCompletion(status);}}
方法解读:整体来说回滚的代码不是很难,可能比较陌生的地方就是几个判断了。
- 回滚匹配规则是什么?就是@Transactional注解里面的
rollBackFor
跟noRollBackFor
注解了。怎么判断,instance of
咯。 - 有安全点吗?上面介绍概念的时候说过,
_NESTED
传播类型是可能会创建安全点的。当然肯定还要其他方式 - 是一个新的事务吗?当方法嵌套,并且被调用的方法事务传播级别是
_NEW
的时候。这个时候就会新开一个事务 rollbackOnly
跟globalRollbackOnParticipationFailure
。这个需要举例说说:
rollbackOnly
,可以认为是确定回滚标志。当经过一系列判断任务真的要回滚的时候,就会设置这个标记为true
。所以,可以认为是确定回滚标志。
显示调用即如下这种方式。正常情况下,a()调用b()方法时,如果b()方法抛了异常,但是在a()方法捕获了,那么a()的事务还是会正常提交的,但是有的时候,我们捕获异常可能仅仅只是不把异常信息返回给客户端,而是为了返回一些更友好的错误信息,而这个时候,我们还是希望事务能回滚的,那这个时候就得告诉Spring把当前事务回滚掉,做法就是:
@Transactional
public void test(){// 执行sqltry {b();} catch (Exception e) {// 构造友好的错误信息返回TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}}public void b() throws Exception {throw new Exception();
}
globalRollbackOnParticipationFailure
这个如果直译的话就很难理解他意思了,默认为true
。直译过来:全局,在部分失败的时候回滚。为true
的时候似乎能理解的了。但是当它为false
的时候,你是不是会这么翻译:当部分失败的时候全局不回滚?
示例如下:
@Component
public class UserService {@Autowiredprivate UserService userService;@Transactionalpublic void test() {// test方法中的sql:insert......userService.a();}@Transactionalpublic void a() {// a方法中的sql:insert......int result = 100/0;}
}
你们猜当为globalRollbackOnParticipationFailure==false
的时候上面怎么回事?是不是以为test
里面的sql插入了,a()
里面的没插入?答案是:都插入了!其实看上面的源码就会发现,当这个值为false
的时候,是不会设置上面说到的回滚标志的。所以,我会选择这样去翻译理解它:globalRollbackOnParticipationFailure==false
表示:已经成功的记录不回滚。
2.11 TransactionInterceptor#commitTransactionAfterReturning:提交
对应流程图:
这个也比较简单就不贴代码了。值得注意的点有1个:那就是提交之前会判断rollbackOnly
属性。如果这个值为true
,就算没有发生异常也照样会回滚。所以上面我才说,这个rollbackOnly
是确定回滚的标志。
2.12 回滚跟提交补充
不知道大家发现上面的【回滚】跟【提交】里面的,诸如:提交前、提前后、完成前、完成后没有。这些是Spring提供给我们的拓展点。Spring事务有可能会提交,回滚、挂起、恢复,所以Spring事务提供了一种机制,可以让程序员来监听当前Spring事务所处于的状态。
@Component
public class UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;@Autowiredprivate UserService userService;@Transactionalpublic void test() {TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void suspend() {System.out.println("test被挂起了");}@Overridepublic void resume() {System.out.println("test被恢复了");}@Overridepublic void beforeCommit(boolean readOnly) {System.out.println("test准备要提交了");}@Overridepublic void beforeCompletion() {System.out.println("test准备要提交或回滚了");}@Overridepublic void afterCommit() {System.out.println("test提交成功了");}@Overridepublic void afterCompletion(int status) {System.out.println("test提交或回滚成功了");}});jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1')");System.out.println("test");userService.a();}@Transactional(propagation = Propagation.REQUIRES_NEW)public void a() {TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void suspend() {System.out.println("a被挂起了");}@Overridepublic void resume() {System.out.println("a被恢复了");}@Overridepublic void beforeCommit(boolean readOnly) {System.out.println("a准备要提交了");}@Overridepublic void beforeCompletion() {System.out.println("a准备要提交或回滚了");}@Overridepublic void afterCommit() {System.out.println("a提交成功了");}@Overridepublic void afterCompletion(int status) {System.out.println("a提交或回滚成功了");}});jdbcTemplate.execute("insert into t1 values(2,2,2,2,'2')");System.out.println("a");}
}
2.13 结束
代码贴图就到此结束了,嘻嘻。我知道斋看我这篇文章大家是不可能读懂源码的,但是,我认为,如果大家能跟着我的流程图,然后一边看源码的话,读懂是没太大问题的。
当前分析就到这里了,回见
学习总结
- 学习了Spring事务的底层原理
- 加深了Spring事务传播类型的印象,从源码上理解了过程