最近在工作中用到了spring的事务管理功能,到项目代码中一看,有声明式的,有编程式的,比较混淆,所以对spring的事务管理做了一个简单的分析,主要回答自己一下几个问题:
1. 声明式事务怎么处理事务?
2. 编程式事务怎么处理事务?
3. 他们之间有什么关系?
4. spring事务管理和传播特性怎么联系起来的?
5. spring事务管理中的核心类是哪几个,分别用来做什么?
了解清楚工作方式后会对代码更有信心.对各位来说下节很多是很容易理解的概念,但为了能说明清楚最后的执行过程,还是想再介绍一下。
一、spring事务的简单介绍
事务,是为了保证在一个方法或代码块中对数据库多次操作的原子性的描述,即事务内的与数据库的多次操作同时成功或同时失败,避免出现数据不一致的情况。
在spring中,提供了两种使用事务的方式:
1. 声明式事务,通过在spring的配置文件中配置隔离级别,传播特性,并结合spring的AOP功能,对所配置的方法做动态代理来达到某方法中的多次数据库操作同时成功,或者同时失败。
2. 编程式事务,通过在方法内部对于数据库的多次操作采用手工事务包裹的方式,达到事务内的数据库操作同时成功,或者同时失败。
二、隔离级别与传播特性的介绍
这里的概念与数据库有莫大的关系:
隔离级别:即数据库对数据的隔离级别,不同的数据库对数据的隔离级别不同,并且可以手工配置,大致分为以下几种,隔离级别由低到高:
Read Uncommitted: 直译就是"读未提交",意思就是即使一个更新语句没有提交,但是别的事务可以读到这个改变.这是很不安全的. ( 隔离级别最低,并发性能高 )
Read Committed: 直译就是"读提交",意思就是语句提交以后即执行了COMMIT以后别的事务就能读到这个改变. (锁定正在读取的行)
Repeatable Read: 直译就是"可以重复读",这是说在同一个事务里面先后执行同一个查询语句的时候,得到的结果是一样的. (锁定所读取的所有行)
Serializable: 直译就是"序列化",意思是说这个事务执行的时候不允许别的事务并发执行. (锁表)
特别说明的:
1. Oralce的默认隔离级别为:Read Committed
2. MySQL中的MySQL InnoDB存储引擎 的默认隔离级别是Repeatable Read
3. 设置隔离级别可以通过SQL 语句: set transaction isolation level xxx 实现
传播特性:
1.PROPAGATION_REQUIRED 如果存在一个事务、则支持当前事务。如果没有事务则开启。
2.PROPAGATION_SUPPORTS 如果存在一个事务、则支持当前事务。如果没有事务则非事务执行。
3.PROPAGATION_MANDATORY 如果已经存在一个事务、则支持当前事务。如果没有活动事务则抛出异常。
4.PROPAGATION_REQUIRES_NEW 总是开启一个新的事务、如果已经存在一个事务、则将这个事务挂起。
5.PROPAGATION_NOT_SUPPORTED 总是非事务执行、并挂起任何存在的事务。
6.PROPAGATION_NEVER 总是非事务执行、如果存在一个活动事务则抛出异常。
7.PROPAGATION_NESTED 果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。
它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。
化成表格后是这样:
事务属性
事务1
事务2
Required
无
事务1
事务2
事务1
RequiredNew
无
事务1
事务2
事务2
Support
无
事务1
无
事务1
Mandatory
无
事务1
抛异常
事务1
NOSupport
无
事务1
无
无
Never
无
事务1
无
抛异常
NESTED无
事务1
Required
事务1,若有回滚,不影响事务1
比较特殊的是最后一个NESTED,注释是这样说的:
而我们项目中使用的是DataSourceTransactionManager他是支持该传播特性的:
三、 声明式事务与编程式事务处理方式的不同
这里涉及的类比较多,自己梳理了一下几个核心类和核心变量的包含关系
接下来进入正题,从源码角度去分析两者的实现方式,总的来说,无论是使用AOP的声明式事务还是使用匿名内部类的编程式事务,都是在具体方法调用前根据传播特性声明一个TransactionStatus,
他是用来记录Transation的一些状态以及Transation本身,不同的是,声明式事务在代理类使用了一个ThreadLocal变量来存放TransationStatus(用TransactionInfo包装),并且还提供了一个备份变量,以供还原。
这么做的好处是:
记录代理类在该方法进入前生成的TransactionStatus的,每次进入一个代理类的时候就新生成一个TransactionStatus放进去,方法调用完返回时再释放,类似一个链条。
这么做的好处是:
记录代理类在该方法进入前生成的TransactionStatus的,每次进入一个代理类的时候就新生成一个TransactionStatus放进去,方法调用完返回时再释放,类似一个链条。
这样,每一个层次的代理类就可以再自己代理范围内对事务做处理,比如,设置回滚,提交(事实上内部事务抛出异常并不会马上回滚,而是设置连接的回滚标志,等到返回链条的最顶端时,才会根据标志决定是否回滚)。
而TransationTemplate里没有用ThreadLocal存放,我的理解是,TransationTemplate并不是单例模式使用的,不需要使用ThreadLocal存放。
那,spring是用什么判定是否需要新建一个事务呢?
从上图中可以看到ConnectionHolder里面有个transactionActive的标志位,而他所在的DataSourceTransactionObject是存放在TransationSynchronizationManger(单例)中的ThreadLoal中的,
简单说就是被当前线程共享的变量中,这样,每次进入一个事务时只需要通过该变量中的标志位和用户配置的传播特性共同决定是否需要新建事务就可以了。
这么做的就解释了 下面传播特性的说明:
”PROPAGATION_REQUIRED 如果存在一个事务、则支持当前事务。如果没有事务则开启“ 这是针对当前线程来说的(我很久才明白这个意思)
对于声明式事务,再说明一下:
AManagerImpl{
methodA{
aDao.insert();
bManagerImpl.methodB();
}
}
这个时候如果 传播特性是PROPAGATION_REQUIRED,在进入methodA之前spring 开启一个事务,当代码进入methodB时,通过ConnectionHolder(包装了connection对象)
里的标志位transactionActive看到已经有一个事务了,根据事务的传播特性,不需要重新new一个事务,所以只是单纯的new了一个TransationStatus,用TransactionInfo包装后,
set在代理类的ThreadLocal对象中。然后继续执行methodB的方法。
里的标志位transactionActive看到已经有一个事务了,根据事务的传播特性,不需要重新new一个事务,所以只是单纯的new了一个TransationStatus,用TransactionInfo包装后,
set在代理类的ThreadLocal对象中。然后继续执行methodB的方法。
下面在通过两张截图,仅从调用上看看两种方式的区别:
声明式事务,spring 用CGLIB 做AOP编程,将我们配置的传播特性等属性封装成对象后,进入最主要的代理类TransactionInterceptor的invoke方法:
在看编程式事务,比较简单,看TransactionTemplate类中的方法:
那他们是在哪一步走入同一个流程呢?这一步困扰我很久,那获取事务举例,其实很简单,画个图说明:
虽然层数比较多,但是还是能很清晰的看到,其实两种方式都是最终走到了AbstractPlatformTransactionManager类中,调用方法getTransaction得到transationStatus,
内部是新创建一个Transation还是使用当前Transation,由TransationSynchronizationManger通过ThreadLocal变量中的标志位和用户配置的传播特性共同决定,
其实,第三节第一张图可以看出,spring事务管理的大部分核心操作都在AbstractPlatformTransactionManager类中进行。
四、收获
在这次分析中,感受最深的是spring对于内部类的使用,DataSourceTransactionObject是DataSourceTransactionManager的内部类,TransactionInfo是TransactionAspectSupport
的内部类,这些类都是为外部类单独使用,用于存放当前线程的状态的。这么做的好处是,不必要单独建一个JAVA文件,并且内部类有一定的保护数据的功能。
其次是抽象类和接口的继承,可以看到,AbstractPlatformTransactionManager下通过多态规定了绝大部分的操作流程,而具体实现根据各个子类所针对的不同情况具体实现。
即,父类,规定流程,子类提供实现,这么做的效果非常好,流程清晰,易扩展,实现多种多样。
附上一张AbstractPlatformTransactionManager的继承关系图: