Spring在事务管理时,对事务的处理做了极致的抽象,即PlatformTransactionManager。对事务的操作,简单地来说,只有三步操作:获取事务,提交事务,回滚事务。
1 | public interface PlatformTransactionManager { |
当然Spring不会仅仅只提供一个接口,同时会有一个抽象模版类,实现了事务管理的具体骨架。AbstractPlatformTransactionManager类可以说是Spring事务管理的控制台,决定事务如何创建,提交和回滚。
在Spring事务管理(二)-TransactionProxyFactoryBean原理中,分析TransactionInterceptor增强时,在invoke方法中最重要的三个操作:
- 创建事务 createTransactionIfNecessary
- 异常后事务处理 completeTransactionAfterThrowing
- 方法执行成功后事务提交 commitTransactionAfterReturning
在具体操作中,最后都是通过事务管理器PlatformTransactionManager的接口实现来执行的,其实也就是上面列出的三个接口方法。我们分别介绍这三个方法的实现,并以DataSourceTransactionManager为实现类观察JDBC方式事务的具体实现。
0. 生成代理
"1. 获取事务
"getTransaction方法根据事务定义来获取事务状态,事务状态中记录了事务定义,事务对象及事务相关的资源信息。对于事务的获取,除了调用事务管理器的实现来获取事务对象本身外,另外的很重要的一点是处理了事务的传播方式。
1 | public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException { |
获取事务的方法主要做两件事情:
- 获取事务对象
- 根据事务传播方式返回事务状态对象
获取事务对象,在DataSourceTransactionManager的实现中,返回一个DataSourceTransactionObject对象
1 | protected Object doGetTransaction() { |
每次执行doGetTransaction方法,即会创建一个DataSourceTransactionObject对象txObject,并从事务同步管理器中根据DataSource获取数据库连接持有对象ConnectionHolder,然后存入txObject中。事务同步管理类持有一个ThreadLocal级别的resources对象,存储DataSource和ConnectionHolder的映射关系。因此返回的txObject中持有的ConnectionHolder可能有值,也可能为空。而不同的事务传播方式下,事务管理的处理根据txObejct中是否存在事务有不同的处理方式。
关于关注事务传播方式的实现,很多人对事务传播方式都是一知半解,只是因为没有了解源码的实现。现在就来看看具体的实现。事务传播方式的实现分为两种情况,事务不存在和事务已经存在。isExistingTransaction方法判断事务是否存在,默认在AbstractPlatformTransactionManager抽象类中返回false,而在DataSourceTransactionManager实现中,则根据是否有数据库连接来决定。
1 | protected boolean isExistingTransaction(Object transaction) { |
当前无事务
如果当前没有事务,则不同事务传播方式的处理如下:
- 事务传播方式为mandatory(强制必须有事务),当前没有事务,即抛出异常。
- 事务传播方式为required或required_new或nested(嵌套),当前没有事务,即会创建一个新的事务状态。
- 其他事务传播方式时,直接返回事务对象为null的事务状态对象,即不在事务中执行。
如何创建一个新的事务状态
1 | // 1. 新建事务状态,返回DefaultTransactionStatus对象 |
第一步,新建事务状态,就是构建一个DefaultTransactionStatus对象
1 | protected DefaultTransactionStatus newTransactionStatus( |
第二步,事务初始化,AbstractPlatformTransactionManager没有实现,来看DataSourceTransactionManager的实现:获取一个新的数据库连接并开启事务,完成事务的基本属性设置。
1 | protected void doBegin(Object transaction, TransactionDefinition definition) { |
第三步:准备同步操作,如果事务状态开启同步,则在事务同步管理器中设置事务基础属性
1 | protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) { |
当前有事务
如果当前已经有事务存在,由handleExistingTransaction方法完成事务操作。
- 传播方式为never(不允许事务),抛出异常
- 传播方式为not_supported(不支持),则挂起当前事务,以无事务方式运行
- 传播方式为required_new,挂起原有事务,并开启新的事务
- 传播方式为nested(嵌套),创建嵌套事务。这里一般方式都是通过savepoint保存点的完成嵌套,但Spring对JTA事务单独做了另一种处理。
- 传播方式为supports或required,返回当前事务
1 | private TransactionStatus handleExistingTransaction( |
这里关注两个点
第一是事务的挂起,Spring并不是真的对数据库连接做了什么挂起操作,而是在逻辑上由事务同步管理器做了事务信息和状态的重置,并将原事务信息和状态返回,并记录在新的事务状态对象中,从而形成一种链式结构。
1 | protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException { |
第二是嵌套事务设置保存点,通常由JDBC3.0支持的savepoint API完成,然后将保存点记录在事务状态中。
1 | DefaultTransactionStatus status = |
至此,完成了获取事务
2.提交事务
首先要说的,commit方法并不是一定提交事务,也可能回滚。
"1 | public final void commit(TransactionStatus status) throws TransactionException { |
processCommit执行事务的提交,但事务的提交也分为几种情况:
- 存在保存点,即嵌套事务,则释放保存点
- 如果事务是由当前事务状态开启的,即事务传播的第一层,执行事务提交
- 其他情况(比如事务是继承自上一层),则不做任何操作
且在processCommit方法中,不同时候设置了不同状态的触发监控,用来提示事务同步相关资源,触发需要的操作。
1 | private void processCommit(DefaultTransactionStatus status) throws TransactionException { |
DataSourceTransactionManager对doCommit的实现,就是执行数据库连接的提交
1 | protected void doCommit(DefaultTransactionStatus status) { |
3.回滚事务
回滚事务时也分为几种情况:
- 存在保存点(嵌套事务),则回滚到保存点
- 如果事务是由当前事务状态开启的,则执行回滚操作
- 其他情况下,如果事务状态设置了回滚标识,则设置事务对象的状态也为回滚,否则不做任何操作
1 | private void processRollback(DefaultTransactionStatus status, boolean unexpected) { |
对于DataSourceTransactionManager实现,回滚保存点和回滚事务都由JDBC的API来完成。
至此,事务管理器对事务的三种操作就简单地介绍完了,但其中事务同步资源的控制十分精妙,这里就不做详细的介绍。有兴趣的自己去研究源码。于我而言,任何强大的机制都是由一行行地源码精妙地组建出来的,深入进去,一切都将真相大白。