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

Stella981
• 阅读 1431

    原文地址: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,表示如果当前没有事务,就新建一个事务,如果存在一个事务,方法块将使用这个事务,具体其他参数的意义请看下图:Spring Transaction + MyBatis  SqlSession事务管理机制研究学习

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

    @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是否为空,如下:

            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也是这样进行管理的,但是问题来了, 先看源码:

            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事务管理机制研究学习

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
4cast
4castpackageloadcsv.KumarAwanish发布:2020122117:43:04.501348作者:KumarAwanish作者邮箱:awanish00@gmail.com首页:
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这