开场白:最近在研究事务

方法1
@Transactional
public int update() {
    testDao.selectRuleResultsListByRuleNoForMap();
    return testDao.updateCreditInfo("2323s","2323s","503");
}
方法2
public int update() {
    testDao.selectRuleResultsListByRuleNoForMap();
    return testDao.updateCreditInfo("2323s","2323s","503");
}

上面方法1为啥在进入方法之前他就先获取连接,而下面方法则是执行到具体的dao时在获取连接,经过研究得知因为加上事务在进入方法前获取连接然后把当前连接放入ThreadLocal中,是因为大家都知道默认的connection是自动提交,所以他会提前获取连接然后自动提交设置为false,后面这个方法不管dao操作几次他都从ThreadLocal中获取Connection,相反如果没有设置事务则是执行到dao时才获取Connection,并且每个dao的Connection有可能不一样,具体源代码如下

大家好,开篇先来谈谈spring事务的优点吧,即spring事务的存在价值。首先它提供了非侵入式编码的事务实现,这个是通过aop实现的,具体的实现过程之前也写博客分析了。

       另外,spring还提供了一套标准的事务管理工作流程。简单的说,事务管理一共可分为三个步骤,分别是初始化事务、提交事务、回滚事务,然后每个步骤又可细分为若干小步骤。spring事务工作流相当于为用户屏蔽了具体orm框架的底层处理逻辑,基于spring开发的程序,即便更换了orm框架,即便从本地事务切换到全局事务,也只需要简单的更改配置,选用合适的事务管理器,基本不会修改代码,这就是spring事务另一个优点。

     

       Spring的PlatformTransactionManager是事务管理的顶层接口,其中定义的三个方法对应的就是上述的三个步骤,然后AbstractPlatformTransactionManager抽象类给出了三个步骤的具体实现。当然,AbstractPlatformTransactionManager也留下了几个未实现的抽象方法,具体有“创建事务对象”,“开始事务”,“执行提交”,“执行回滚”,这几个抽象方法的实现跟具体的orm框架(如:mybatis,hibernate)或事务特性(如:本地事务,全局事务)有关。比如今天要介绍的主角DataSourceTransactionManager,它实现了上述的抽象方法,它适用的场景是“mybatis”,“ jdbctemplate”,"本地事务"。下面我们就具体分析DataSourceTransactionManager的事务管理三大步骤详细处理流程。

一. DataSourceTransactionManager初始化事务

1. 获取事务名称 

    默认事务名称是被代理类的全限定名称+当前被拦截的方法名称,如: testmybatis.TxDao.findList。

2. 获取事务属性对象(TransactionDefinition)

    事务属性对象持有事务的相关配置,比如事务的隔离级别,传播行为,是否只读等。我们开启spring事务管理时,通常都会在配置文件里加入这样一段配置。

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="get*" read-only="true"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>
     spring解析上述配置会创建两个事务属性对象。第一个事务属性对象适用于get前缀的方法对象,它是只读事务;第二个事务对象匹配非get前缀的其它方法,它使用默认事务配置,默认配置的话非只读,事务超时时间为-1,隔离级别使用数据库默认配置,传播行为是PROPAGATION_REQUIRED。

3. 获取事务管理器

    事务管理器跟事务属性对象一样,通常用户都已经配置好了,直接从spring的bean工厂获取,另外从配置也看出DataSourceTransactionManager持有dataSource。

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="jdbcDataSource" />
</bean>
4. 创建事务对象
    这个方法是AbstractPlatformTransactionManager定义的抽象方法,DataSourceTransactionManager实现了这个抽象方法,它创建的事务对象类型是DataSourceTransactionObject,事务管理器和事务对象之间是存在对应关系的。然后尝试从当前线程获取ConnectionHolder(连接持有器),赋值给事务对象。ConnectionHolder实现了ResourceHolder接口,它持有一个数据库连接,可以把凡是实现了ResourceHolder接口的对象看做是一种资源对象。默认情况下此时当前线程是不存在ConnectionHolder的,因此获取不到。插播一句,在spring事务管理过程中会用到一些线程安全对象,这些对象都交由TransactionSynchronizationManager管理,TransactionSynchronizationManager把这些对象都保存在ThreadLocal中。

5. 获取被挂起资源持有器

    被挂起资源持有器持有两类被挂起的资源,一类是资源对象,如上述的ConnectionHolder,一类是同步对象,即实现了TransactionSynchronization接口的对象。默认在这个时候事务同步还未被激活,因此返回null。

6. 创建事务状态对象

    被创建的事务状态对象类型是DefaultTransactionStatus,它持有上述创建的事务对象。事务状态对象主要用于获取当前事务对象的状态,比如事务是否被标记了回滚,是否是一个新事务等等。

7. 开始事务 

    7.1. 由于此时事务对象DataSourceTransactionObject持有的ConnectionHolder为null,因此首先需要创建ConnectionHolder对象,创建ConnectionHolder对象的前置条件是要先获取数据库连接对象,于是从dataSource获取连接对象,把连接设置成手动提交,完成ConnectionHolder对象的创建,然后赋值给事务对象。另外这个时候上述的事务属性对象派上用处了,根据事务属性对象配置连接的相关属性,如隔离级别、超时时间等。

    7.2. 把资源对象ConnectionHolder置入线程安全的map,key是dataSource对象,value是ConnectionHolder对象本身,当orm框架执行数据库操作需要连接对象时,获得就是这个ConnectionHolder持有的连接。

8. 预准备事务同步(主要是调用TransactionSynchronizationManager对象的方法,设置事务相关同步对象)

    8.1. 设置当前线程事务激活为true。

    8.2. 把事务隔离级别设置到当前线程。

    8.3. 把事务是否只读设置到当前线程。

    8.4. 把事务名称设置到当前线程。

    8.5. 实例化当前线程的同步对象,默认是一个空集。

9. 创建事务信息对象(TransactionInfo)

    事务信息对象持有“事务管理器”,“事务属性对象”,“事务状态对象”,可以把事务信息对象看做对以上几个对象的打包。然后把事务信息对象置入当前线程。

    

 

二. ORM框架(mybatis)执行数据库操作

1. 通过mybatis's DefaultSessionFactory获取defaultSqlSession。

2. 根据defaultSqlSession创建SqlSessionHolder(资源对象),把SqlSessionHolder置入当前线程,key是DefaultSessionFactory。

3. 创建SqlSessionSynchronization(同步对象),它持有的资源对象是SqlSessionHolder,把同步对象注册到当前线程。

4. 从当前线程获取ConnectionHolder对象,key是dataSource,从ConnectionHolder获取连接对象,

5. 执行数据库操作。

关于同步对象,资源对象和事务之间的关系请看下图:

三. DataSourceTransactionManager回滚事务

1. 判断是否有必要对抛出的异常执行回滚操作,默认只要异常对象派生自RuntimeException或Error,都会执行回滚操作。这个用户是可以配置的,比如这样。

<tx:attributes>
    <tx:method name="*" rollback-for="com.cn.untils.exception.XyzException"/>
</tx:attributes>
2. 调用事务管理器的rollback(transactionStatus)方法,传入事务状态对象。
    2.1. 触发同步对象的beforeCompletion方法。

    2.2. 从事务对象获取连接持有器,然后再获取连接对象,调用连接的rollback()方法。

    2.3. 触发同步对象的afterCompletion方法。

四. DataSourceTransactionManager提交事务

1. 调用事务管理器的rollback(transactionStatus)方法,传入事务状态对象。

    1.1. 触发同步对象的beforeCommit方法。

    1.2. 触发同步对象的beforeCompletion方法。

    1.3. 从事务对象获取连接持有器,然后再获取连接对象,调用连接的commit()方法。

    1.4. 触发同步对象的afterCommit方法。

    1.5. 触发同步对象的afterCompletion方法。ro
 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐