由于之前写的一篇MyBatis原理源码文章点赞和收藏的用户比较多,应该是写的不错,最近正好在回顾Spring事务的源码,趁这个机会再写一篇关于事务源码,让读者能对事务有个全新的认识,同样事务的源码流程我之前也画了一张流程图,需要的自取Spring事务执行流程图,下面是processon的部分流程图
如果要让一个操作数据库的方法以事务方式执行,我们只需要在该方法上使用@Transaction
注解,然后我们就不用管了,方法正常执行的话会自动提交,异常会自动回滚。
注解的属性我们可以先忽略,后面再说,其实Spring事务的实现就是基于AOP来实现的,在容器启动过程中,创建Bean时,会判断该类或者方法中是否有被@Transaction
注解修饰,如果有,那么就会为该类创建代理类,也就是把Spring给我们做的事务相关操作织入到原有的逻辑。我们都知道Spring的AOP有around
、before
、after
等,他们都会各自的通知Advisor
,那么事务的Advisor
是哪个呢?TransactionInterceptor,该类就是事务逻辑的Advisor
。
老规矩,大家先跟一些重要的类混个脸熟,不要等下后面看到这些类,一脸懵逼
存储了@Transaction注解的一些信息,隔离级别、传播特性,我们看下该对象具体有哪些属性值,通过下图我们知道该对象就是包装了用户使用@Transaction
注解时指定的属性值
Spring命名还是很规范的,见名知意,事务管理器,我们看下该类具体有些啥属性值
事务状态对象,该对象保存着事务运行过程中的一些属性,当前是否是一个新事物、事务保存点、是否需要回滚、还保存着挂起事务的连接资源我们看下该对象运行过程的属性值
事务的定义信息,包含了执行事务的方法、方法的一些传播特效、隔离级别等
该对象就是包含了数据库连接的Connection对象
被挂起的事务的资源信息
事务中最重要的一个类就是,该对象围绕了整个事务执行过程,它包含了数据源对象(TransactionManager
)、事务个隔离级别、传播特性(TransactionAttribute
)、当前事务状态对象(TransactionStatus
)、还有被挂起的事务的TransactionInfo
存储了一个事务执行过程中的属性和资源信息,本质就是6个ThreadLocal
该图包含了以上所有的类,并总结了各自的作用
由于事务是基于AOP实现的,我们直接找到AOP的入口,DynamicAdvisedInterceptor#intercept
(该类是CglibAopProxy的一个内部),先找到事务的通知类TransactionInterceptor
,然后执行它的invoke
方法,执行完后增强逻辑后,再执行具体的方法代码(被@Transaction
注解修饰的方法)
这里是过渡方法调用,没有多大分析的意义,直接过
下面这个方法可以看出整个增强逻辑做的事情,框住的每个方法比较重要,先总结该方法做了哪些事情
@Transactional
注解的属性值那过来封装了一下)completeTransactionAfterThrowing
并没有对事务进行回滚,而是设置了回滚的标志位,后面我们看提交代码的时候就能看到,该方法我们也需要提出去单独分析该方法主要做了两件事:
oldTransactionInfo
属性中,当前事务执行完后,需要恢复被挂起事务的TransactionInfo,重新设置到transactionStatus
属性中)DataSourceTransactionObject
,获取了当前线程中是否存在ConnectionHolder
,存在的话说明之前已经存在事务了MANDATORY
直接抛异常,如果是REQUIRED、REQUIRES_NEW、NESTED
,会挂起一个空事务(因为之前就没有事务所以是个空事务),然后会创建一个新的事务,剩下的传播特性的话都会创建一个空的事务这里就是根据事务传播特性来处理已经存在的事务
事务挂起就是将需要挂起事务的数据从TransactionSynchronizationManager
各个缓存对象中取出来(其中包括了事务的名称、只读属性、隔离级别、事务活跃状态)然后将这些数据封装到SuspendedResourcesHolder
对象中,然后将SuspendedResourcesHolder
对象引用存到新建事务对象DefaultTransactionStatus#suspendedResources
中,等新事务执行完毕后,会恢复被挂起的事务,然后将这些数据再存到TransactionSynchronizationManager
对象中
对事务挂起操作进行反操作,挂起时将事务的属性信息都封装到了SuspendedResourcesHolder对象中,我们只需要原样放回去就好
创建一个新的事务,主要就是将事务的定义信息,数据源事务对象、被挂起事务信息等来创建,一个新的事务肯定会创建一个新的连接对象
创建Connection对象
会设置当前事务的隔离级别、关闭连接的自动提交、然后将连接设置到事务数据源对象的ConnectionHolder
属性中,最后将连接对象和当前线程进行绑定
如果事务执行过程中,发生了异常,会根据异常类型来判断是否需要进行回滚,Spring默认只会对RuntimeException 和Error进行回滚操作,这也就是为啥我们在使用注解时一定要指定 @Transactional(rollbackFor = Exception.class)
,如果不指定是不会回滚的,不回滚就会对事务进行提交操作。
中间跳过了一个rollback
方法,里面没做啥事,就是调用了processRollback
方法,下面我们对processRollback
方法进行分析
这一块不管事务是提交还是回滚都会执行事务数据清理,不管先前是否存在事务,当前事务已经结束了,需要将当前事务的数据从线程绑定中解除(之前存在ThreadLocal中的值),如果是一个全新事务,我需要把我的连接状态重置,然后释放连接,如果存在被当前事务挂起的线程,需要恢复挂起事务(上面已经讲过事务的挂起和恢复)。
这一块代码放在finally
里面,不管事务执行结果如何,都会执行。我们之前说过,如果当前事务之前还存在事务的话,会将老的事务信息从TransactionAspectSupport#transactionInfoHolder
移除,并将自己的事务信息设置进去,并将老事务信息存储在当前事务的oldTransactionInfo属性中,执行到该方法,说明当前事务肯定执行完了,所以我们需要将oldTransactionInfo
设置回TransactionAspectSupport#transactionInfoHolder
不管事务执行结果如何,都会执行该方法,会处理用户自己设置了回滚标记的逻辑(TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
),如果事务设置了全局回滚标记也会进行回滚操作,如果事务没有被设置回滚标记的话,下面就会开始处理事务的提交了
全局事务标记这一块我也不是很理解,第一个条件
shouldCommitOnGlobalRollbackOnly()
默认值为false
,加上取反,也就是一定会走第二个条件defStatus.isGlobalRollbackOnly()
,但是该方法获取的还是当前连接的rollbackOnly
属性,而上面处理回滚标记已经对该条件进行了判断处理,按道理该条件是不会进的呀
如果当前事务有保存的话,代码执行到这,说明要进行真正的commit
操作,之前嵌套事务设置的连接的保存点也就没有存在的必要,需要将它清除。
如果事务是一个全新的事务,那么执行到这说明事务执行完了,该执行Connection
对象的commit
方法啦,如果当前事务只是参与到别的事务当中,相当于做了一个空提交,当前事务只是事务中的一部分,并不是全新事务,提交与否还得取决于其它参与该事务的事务成员来决定,不管哪个参与事务的事务成员执行发生异常,都会进行回滚工作,但是提交工作必须为第一个开启事务的那个事务(第一个被@Transactional
注解的方法)
其实事务的源码还是比较好理解的,首先要知道它是基于AOP来实现的,明白事务传播特性,搞清楚各个特性的作用,执行流程主要分为三大步
观看过程中,发现我说的有不对的地方,请大佬指出,请不要让我误人子弟。