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