spring事务和事务传播
spring事务和事务传播
1.原生的事务控制是这样的
原生的事务是这样使用的,需要将连接设为非自动提交,执行完sql语句后,可以选择提交或者回滚。
1 |
|
除了直接提交外,还可以设置savepoint,可以回滚到指定的savepoint。
1 |
|
2.spring的事务控制
spring的事务传播级别有七种,分为三类
1.必须要在事务里运行的
事务级别 | 解释 |
---|---|
REQUIRED | 如果当前存在事务,加入当前事务,否则创建新的 |
REQUIRES_NEW | 如果当前存在事务,则挂起他,开启新的事物执行 |
MANDATORY | 如果当前存在事务,加入当前事务,否则报错 |
NESTED | 如果当前存在事务,使用嵌套事物执行,否则开启新事务 |
2.必须以非事务运行的
事务级别 | 解释 |
---|---|
NOT_SUPPORTED | 以非事务方式执行,如果当前存在事务,则挂起他 |
NEVER | 以非事务方式执行,如果当前存在事务,则报错 |
3.有没有事物都可以运行的
事务级别 | 解释 |
---|---|
SUPPORTS | 当前存在事务就加入,没有就已非事务方式执行 |
Spring是使用Aop对方法进行拦截实现自动管理事务的,在方法前后进行拦截。
1 |
|
如何保证方法内获取的连接就是ThreadLocal上存储的连接呢?
获取连接,可以使用DataSourceUtils
工具类,如果当前ThreadLocal存在连接,它会返回ThreadLocal上的,否则才返回数据源内的。
1 |
|
如果使用的是MyBatis
,Mybatis
获取连接的是使用Transaction
对象获取连接的。整合Spring时使用的是SpringManagedTransaction
对象,其会按照Spring的规范,使用DataSourceUtils获取连接。
如果使用的是JdbcTemplate
,此对象获取连接方法也是使用的DataSourceUtils工具类。
其他orm框架要想使用Spring的事物管理,都需要使用DataSourceUtils工具类获取连接,这样才能被Spring管理。
下面是DataSourceUtils工具类获取连接的实现逻辑
先从ThreadLocal上获取,没有再从连接池上获取的逻辑就是工具类DataSourceUtils实现的。
1 |
|
无论使用的是什么orm框架,只要执行Sql时使用的是ThreadLocal里面的连接,就能被Spring的事务管理器管理。
3.事物传播属性的影响
例如下面这样有一个TestController,调用TestService的方法。Controller 和 Service都开启了事物。
1 |
|
如果getItem事物传播级别是 REQUIRED
getItem
方法会加入当前事物,共享同一条连接,加入之前会先调用connection.savePoint()
保存调用点,getItem
里面回滚不会回滚整个事物,而是回滚到savePoint
。getItem
执行完成commit
也不会提交事物,而是清除savePoint
。
如果getItem事物传播级别是MANDATORY
同REQUIRED,加入当前事物
如果getItem事物传播级别是 SUPPORTS
同REQUIRED,加入当前事物。
如果getItem事物传播级别是 REQUIRES_NEW
先将当前ThreadLocal里的连接存储起来,如 oldConnection = ThreadLocal.get(),再清空ThreadLocal。
执行getItem时,因为此时ThreadLocal上已经没有连接了,会新建一个连接。
getItem
执行完成提交或者回滚后,将刚才存储再临时变量oldConnection的连接重新设置到ThreadLocal。
这样将ThreadLocal连接转移到临时变量里,就叫做对该连接挂起。
如果getItem事物传播级别是 NESTED
开启嵌套事物。
如果getItem事物传播级别是 NOT_SUPPORTED
从线程池里获取新连接,使用新连接执行。
如果getItem事物传播级别是 NEVER
因为当前已经存在事物,抛出**Existing transaction found for transaction marked with propagation 'never'
异常。
如果Controller上没有事物,只是Service上有事物
1 |
|
如果getItem事物传播级别是REQUIRES
因为当前ThreadLocal不存在连接,会开启新事物连接。
如果getItem事物传播级别是REQUIRES_NEW
会开启新事物连接。
如果getItem事物传播级别是 NESTED
会开启新事物连接。
如果getItem事物传播级别是 MANDATORY
当前没有事物连接,会抛出No existing transaction found for transaction marked with propagation 'mandatory'
异常。
如果getItem事物传播级别是 NOT_SUPPORTED
从连接池里拿到新的连接执行(没有事物管理)。
如果getItem事物传播级别是 NEVER
从连接池里拿到新的连接执行(没有事物管理)。
如果getItem事物传播级别是 SUPPORTS
从连接池里拿到新的连接执行(没有事物管理)。
原理分析
核心原理就是Spring事先从连接池获取连接,与线程绑定,代码里要使用DataSourceUtils来获取连接,这样能保证获取的连接就是Spring管理的那一条连接。这样Spring就可以在方法执行完成后执行commit 或者捕获到特定的异常时rollback。
对于事物传播属性,就是定义事物方法和事物方法之间,或非事物方法和事物方法之间的调用处理逻辑。
再重复下上面的那张表
传播属性 | 解释 |
---|---|
REQUIRED | 有就加入,没有就新建,最终会在事物中执行 |
REQUIRES_NEW | 有就先挂起旧连接,创建新的连接,最终会在新的事物连接中执行 |
MANDATORY | 要求当前必须存在事物,不存在就报错 |
NOT_SUPPORTED | 获取一个非事物连接执行,不需要挂起旧的,因为新连接不需要事物管理 |
NEVER | 要求当前必须不存在事物,和MANDATORY相反 |
SUPPORTS | 存在就加入,不存在就使用非事物执行 |
NESTED | 存在就使用嵌套事物,不存在就创建 |
一些名词解释
加入当前事物
当前线程ThreadLocal已经存在一个connection,直接拿来用,用之前创建savePoint,如果rollback,只是rollback到savePoint不会全部回滚,commit也不会真的commit,而是清除savePoint。
挂起当前事物
因为当前ThreadLocal上已经绑定了connection,要想创建新的事物连接,需要将ThreadLocal上的connection先临时存储到其他地方,这就叫做挂起。新连接执行完成后,再将存储的旧连接还原回去。
一些重要的类
此方法就是使用ThreadLocal绑定资源的类,注重看下getResource
方法
1 |
|
此方法利用TransactionSynchronizationManager
保存一条连接到ThreadLocal上,如果没有就利用连接池获取新连接。
1 |
|
此方法就是事物管理器根据不同传播属性进行不同方式的处理,需要重点看这个方法的源码。
1 |
|
此方法就是获取当前线程绑定的连接,其利用了TransactionSynchronizationManager
获取当前线程上绑定的连接。
1 |
|
这两个方法就是如果需要挂起当前连接是的操作,它会解绑当前ThreadLocal上的连接
1 |
|
此方法重点看,其实对事物方法进行拦截时的操作,方法执行前通过事物管理器创建事物(其实就是创建连接,绑定到ThreadLocal上),使用try catch捕捉异常,决定是否回滚,最后在commit。
1 |
|
此方法就是进行事物commit操作的,但并不一定真实提交,如果当前存在savePoint说明是加入到其他连接里的,提交操作就是清除savePoint而已。
1 |
|
这是提交或回滚后清理各种资源你的方法,如果SuspendedResources
不为空的话,说明现在还有一个事物被挂起呢,将获取SuspendedResources
,将其还原回去。
1 |
|
总结
以上就是我对Spring事物的看法,逻辑不复杂,但代码分支太多,可以先从整体把握,再逐渐看细节。
再重复一遍简单的原理:
使用Aop对方法调用前,调用后进行拦截,调用前获取连接设置到ThreadLocal上,调用后将ThreadLocal上的连接回滚或提交。
遇到需要开启新事物的,将当前事物挂起,处理新事物,新事物提交后,还原当前事物连接。
更新
AbstractPlatformTransactionManager类上有一个setTransactionSynchronization方法。它可以控制,如果方法受事务管理器管理,但是不需要开启事务的场景下,是否共用同一条jdbc链接。
即一个方法添加了注解,受事务管理,但不需要开启新事物,方法内的若干个请求是否使用同一条数据库连接。默认是开启的,可通过上面那个方法进行控制。
举个例子,如果事务传播级别为NEVER,且外层没有开启事务。这时因为是NEVER级别所以不会开启新的事物,但是方法内执行的多个sql语句是被限制使用同一个sql连接的。
同理NOT_SUPPORTED也可以达成这种条件。
关注下 SYNCHRONIZATION_ALWAYS,SYNCHRONIZATION_ON_ACTUAL_TRANSACTION,SYNCHRONIZATION_NEVER 三个字段。