Spring Transaction + MyBatis SqlSession事务管理机制研究学习

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Transaction + MyBatis SqlSession事务管理机制研究学习相关的知识,希望对你有一定的参考价值。

线上的系统中,使用的是Spring+Mybatis+mysql搭建的框架,由于客户需要,最近一直在对性能提升部分进行考虑,主要是涉及Mysql的一些重要参数的配置学习,以及Spring事务管理机制的学习,因为通过观察服务器日志,发现在这两部分的时候耗时比较严重,特别是进行mysql事务提交的时候,项目源码中使用了Spring的声明式事务,即通过@Transactional注解来控制事务的开启与提交,这两天看了一些关于Spring Transaction事务的一些文章,也debug了源码,总算有点心得和疑问,这里简单的整理一下。


    在Spring的配置文件中,我们使用了"org.springframework.jdbc.datasource.DataSourceTransactionManager"对事务进行管理,翻开DataSourceTransactionManager的源码,我们看到DataSourceTransactionManager继承了AbstractPlatformTransactionManager(抽象的事务管理器),DataSourceTransactionManager重写了其中的一些方法,具体每个方法的作用,限于篇幅,本文不再赘述,这里《Spring技术内幕》学习笔记16——Spring具体事务处理器的实现有详细的介绍。


     在现有的项目中,我们在public方法上面使用了@Transactional注解,当有线程调用此方法时,Spring会首先扫描到@Transactional注解,进入DataSourceTransactionManager继承自AbstractPlatformTransactionManager的getTransaction()方法,在getTransaction()方法内部,会调用doGetTransaction()方法,@Transactional的注解中,存在一个事务传播行为的概念,即propagation参数,默认等于PROPAGATION_REQUIRED,表示如果当前没有事务,就新建一个事务,如果存在一个事务,方法块将使用这个事务,具体其他参数的意义请看下图:

 

技术分享

 

 在getTransaction方法中,DataSourceTransactionManager重写了isExistingTransaction()方法,用于判断当前是否存在事务,以下是其的源码:

1
2
3
4
5
6
    @Override
    protected boolean isExistingTransaction(Object transaction) {
        logger.debug(Thread.currentThread().getName() + ">>>" "DataSourceTransactionManager.isExistingTransaction()");
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        return (txObject.getConnectionHolder() != null && txObject.getConnectionHolder().isTransactionActive());
    }

    但是,这几天我对源码进行调试的过程中,发现多线程并发的时候,isExistingTransaction方法总是返回的false,即ConnectionHolder总是为空,这是遇到的第一个疑问点,目前还没有弄清楚。由于源码判断当前不存在事务,所以总是会Creating new transaction,即新建一个事务。新建事务之后,会执行重写的doBegin()方法,在doBegin方法中,首先通过下面的代码判断了ConnectionHolder是否为空,如下:

1
2
3
4
5
6
7
8
9
            if (txObject.getConnectionHolder() == null
                    || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                Connection newCon = this.dataSource.getConnection();
                if (logger.isDebugEnabled()) {
                    logger.debug(Thread.currentThread().getName() + ">>>" "Acquired Connection [" + newCon
                            "] for JDBC transaction");
                }
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
            }

 

这里会从当前配置的数据源中获取一个连接,然后设置相应的ConnectionHolder,接下来是关键的一步,也是存在的第二个问题点,拿到connection后,会首先判断connection的autoCommit属性是否为true,之前工作中在使用原始JDBC的时候,当进行事务的控制时,我们总是会首先设置autoCommit为false,禁止事务自动提交,然后commit提交事务,最后设置autoCommit为true。Spring Transaction也是这样进行管理的,但是问题来了, 先看源码:

 

1
2
3
4
5
6
7
8
9
10
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                if (logger.isDebugEnabled()) {
                    logger.debug(Thread.currentThread().getName() + ">>>" "Switching JDBC Connection [" + con
                            "] to manual commit");
                }
                con.setAutoCommit(false);
                logger.debug(Thread.currentThread().getName() + ">>>" "Set Transaction AutoCommit False [" + con
                        "]");
            }

  这里有个Swithching JDBC Connection的操作,就是为了设置autoCommit为false,但是在这几天进行多线程并发测试的时候,发现这一部分的代码耗时非常严重,这是目前还不清楚的第二点。


    接下来,开始涉及MyBatis SqlSession部分的一些机制,关于MyBatis sqlSession的一点整理,SqlSession主要是MyBatis定义的用于进行持久化操作的对象,对connection进行了包装。在上面Spring Transaction的机制中,我们获取到了connection,之后会首先调用SqlSessionUtils里面的getSession方法,判断当前的sqlSessionHolder以及是否存在事务锁定。

 

如果不存在sessionHolder或resources未被事务锁定,就会Creating a new SqlSession,然后为sqlSession注册事务资源,即Registering transaction synchronization for SqlSession。之后把connection交给Spring管理;如果存在可用的sessionHolder并且被事务锁定,就会从当前的事务中拿到SqlSession,即Fetched SqlSession from current transaction。当代码中涉及数据库的操作时,就可以从数据源中获取到相应的connection,即Using Connection,这时候就有了第三个问题,在进行debug源码测试的时候,发现using connection这一步的耗时也比较严重。


    在使用完连接后,SqlSessionUtils会释放事务的SqlSession,即Releasing transactional SqlSession。接下来,就可以准备执行事务的提交了,即Initiating transaction commit,这里会调用DataSourceTransactionManager中重写的doCommit()方法,在其中进行事务的提交操作,即Committing JDBC transaction on Connection,为当前的连接提交事务。这里又有了第四个问题,进行事务提交的操作,在进行多线程并发测试的时候,发现耗时非常严重。也尝试过修改连接池或者mysql的配置,问题总是得不到解决。


    在事务提交之后,又回到SqlSessionUtils执行其中的afterCompletion方法,进行MyBatis SqlSession层面的处理。SqlSessionUtils会首先Transaction synchronization committing SqlSession,提交SqlSession,然后关闭SqlSession,即Transaction synchronization closing SqlSession,在MyBatis层面处理完成后,会再次回到DataSourceTransactionManager,执行其中的doCleanupAfterCompletion方法,释放一些资源,包括: Releasing JDBC Connection 释放JDBC连接,Returning JDBC Connection to DataSource 将连接放回到数据源。


    至此,对于Spring Transaction + MyBatis SqlSession事务管理机制,已经做了大致的研究学习,阅读了其中涉及的源码,以及参阅了一些网上的博客,有了一些自己的认识,很近自己的理解整理出了一个简单的时序图,其中也有一些疑问,最后做一下记录。


    主要是包括四个疑问点:

  • 问题1: 在现有框架下isExistingTransaction返回false,即ConnectionHolder为空

  • 问题2: 切换JDBC connection属性,主要是setAutoCommit为false,禁止事务自动提交,耗时不稳定,有时很慢

  • 问题3: 使用数据源中的connection时,connection获取耗时有时很慢

  • 问题4: Spring Transaction再进行事务提交时commit耗时严重


    这里根据自己的理解整理出了一个简单的时序图,共享给大家,其中一定涉及一些不合理甚至理解错误的地方,希望大家不吝赐教。

 技术分享

 









以上是关于Spring Transaction + MyBatis SqlSession事务管理机制研究学习的主要内容,如果未能解决你的问题,请参考以下文章

Spring 事务Transaction源码深度解析

spring---transaction---事务的配置

spring transaction注解原理

Spring Transaction属性之Propagation

Python AOP Spring transaction decorator

Spring Boot Stomp-Transaction Sockjs: ERROR message=[Bad transaction] Invalid transaction identifier