美文网首页Java面试程序员我爱编程
关于Spring+Mybatis事务管理中数据源的思考

关于Spring+Mybatis事务管理中数据源的思考

作者: 1d96ba4c1912 | 来源:发表于2017-09-17 11:10 被阅读171次

    之前被同事问了一个问题:在我们的工程里,事务的开启跟关闭是由Spring负责的,但具体的SQL语句却是由Mybatis执行的。那么问题来了,Mybatis怎么保证自己执行的SQL语句是处在Spring的事务上下文中?

    注:这篇文章重点不是分析Spring事务的实现原理,但却需要读者提前了解Spring事务原理的一些知识点,这样读起来才会容易些

    现在公司主流的开发框架大部分是使用spring+mybatis来操作数据库,所有的事务操作都是交给spring去管理。当我们需要一个有事务上下文的数据库操作时,我们的做法就是写一个操作数据库的方法,并在该方法上面加上@Transactional注解就可以了。

    仔细思考一下这个过程,@Transactional是由spring进行处理的,spring做的事情是从数据源(一般为数据库连接池,比如说druid,c3p0等)获取一个数据库连接,然后在进入方法逻辑前执行setAutoCommit(false)操作,最后在处理成功或者出现异常的时候分别执行commit或者rollback操作。

    那么问题来了,开启跟结束事务是由spring获取到数据库连接以后进行操作的,但我们实际执行的update或者insert语句却是由mybatis获取数据库连接进行操作的,可以想到如果想让事务生效,那么spring跟mybatis使用的必须是同一个连接,真实情况是什么样呢?它们之间如何进行无缝衔接?让我们通过源码来分析一下。

    首先如果想在spring中使用mybatis,我们除了引入mybatis依赖以外,还需要引入一个包:mybatis-spring。

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>x.x.x</version>
    </dependency>
    

    可以猜测这个依赖包应该就是Spring跟Mybatis进行无缝连接的关键。

    一般来说,我们在工程中的配置文件往往是这样:

    <!--会话工厂 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource" />
    </bean>
    
    <!--spring事务管理 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource" />
    </bean>
    
    <!--使用注释事务 -->
    <tx:annotation-driven  transaction-manager="transactionManager" />
    

    注:
    1.会话工厂sqlSessionFactory跟Spring事务管理器transactionManager所使用的数据源dataSource必须是同一个。
    2.这里的sqlSessionFactory类型是org.mybatis.spring.SqlSessionFactoryBean,该类是由我们引入的包mybatis-spring提供的。

    看名字就知道SqlSessionFactoryBean是一个工厂bean,也就是说它交给Spring的真正实例是由getObject()方法提供的,那么我们去看下它真正实例初始化源码:

    @Override
    public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
          //可以看出逻辑都在这里面
          afterPropertiesSet();
        }
        return this.sqlSessionFactory;
    }
    
    @Override
    public void afterPropertiesSet() throws Exception {
        //此处省略一些校验逻辑
        //...
        this.sqlSessionFactory = buildSqlSessionFactory();
    }
    
    //最后来看这个最核心的方法
    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
        //...
        //省略一些其他初始化信息,我们重点关注事务处理逻辑
        
        if (this.transactionFactory == null) {
          //可以看出,mybatis中把事务操作交给了SpringManagedTransactionFactory去做
          this.transactionFactory = new SpringManagedTransactionFactory();
        }
    
        configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
       
       //省略后续逻辑
       //...
    }
    

    下面我们再去看看SpringManagedTransactionFactory类的源码:

    public class SpringManagedTransactionFactory implements TransactionFactory {
    
      /**
       * {@inheritDoc}
       */
      @Override
      public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        return new SpringManagedTransaction(dataSource);
      }
    
      /**
       * {@inheritDoc}
       */
      @Override
      public Transaction newTransaction(Connection conn) {
        throw new UnsupportedOperationException("New Spring transactions require a DataSource");
      }
    
      /**
       * {@inheritDoc}
       */
      @Override
      public void setProperties(Properties props) {
        // not needed in this version
      }
    
    }
    

    代码很少,且只有一个方法是有效的,看来离成功越来越近了,继续跟进去看看SpringManagedTransaction的源码:

    @Override
      public Connection getConnection() throws SQLException {
        if (this.connection == null) {
          openConnection();
        }
        return this.connection;
      }
    
      private void openConnection() throws SQLException {
        this.connection = DataSourceUtils.getConnection(this.dataSource);
        this.autoCommit = this.connection.getAutoCommit();
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
      }
    

    省略该类中其他部分,我们重点看获取连接的地方,这里最关键的地方就在this.connection = DataSourceUtils.getConnection(this.dataSource);
    DataSourceUtils全名是org.springframework.jdbc.datasource.DataSourceUtils,没错,它是由Spring提供的类,根据我们之前的猜测,Spring开启事务以后,Mybatis要想让自己的SQL语句处在这个事务上下文中操作,那必须拿到跟Spring开启事务同一个数据库连接才行,由于DataSourceUtils类是由Spring提供的,看来跟我们开始猜测的结果类似,我们接下来看看DataSourceUtils源码验证一下:

    //获取数据库连接最终落在该方法上,我删除一些不重要的代码
    public static Connection doGetConnection(DataSource dataSource) throws SQLException {
            //TransactionSynchronizationManager重点!!!有没有很熟悉的感觉??
            ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
            if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) {
                Connection con = fetchConnection(dataSource);
                if (TransactionSynchronizationManager.isSynchronizationActive()) {
                    ConnectionHolder holderToUse = conHolder;
                    if (conHolder == null) {
                        holderToUse = new ConnectionHolder(con);
                    } else {
                        conHolder.setConnection(con);
                    }
    
                    holderToUse.requested();
                    TransactionSynchronizationManager.registerSynchronization(new DataSourceUtils.ConnectionSynchronization(holderToUse, dataSource));
                    holderToUse.setSynchronizedWithTransaction(true);
                    if (holderToUse != conHolder) {
                        TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
                    }
                }
                return con;
            } else {
                conHolder.requested();
                if (!conHolder.hasConnection()) {
                    conHolder.setConnection(fetchConnection(dataSource));
                }
    
                return conHolder.getConnection();
            }
        }
    

    看到TransactionSynchronizationManager有没有很亲切的感觉?对Spring事务管理源码熟悉的同学会马上联想到Spring开启事务以后,就是把相应的数据库连接放在这里,我截取源码看一下:

    if (txObject.isNewConnectionHolder()) {
        TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
    }
    

    这段代码具体就是在我们上面配置的org.springframework.jdbc.datasource.DataSourceTransactionManager类中的doBegin方法里。至于TransactionSynchronizationManager类的实现原理其实我觉得你已经猜到了,没错,就是Java中经典类库ThreadLocal类!!!

    最后补上一张图来说明spring+mybatis事务过程数据源获取逻辑:

    Spring-Mybatis事务处理过程

    相关文章

      网友评论

      • 蕪園樓主香獨秀:大体意思就是有@Transactional 的由TransactionsManager 通过datasource 获取 Connection 存储在 ThreadLocal 中,无@Transaction 的就手动通过 datasource 获取 Connection
        1d96ba4c1912:@蕪園樓主香獨秀 正解,其实这个问题也能想到肯定是用ThreadLocal做的,只是不看下源码不放心:smile:

      本文标题:关于Spring+Mybatis事务管理中数据源的思考

      本文链接:https://www.haomeiwen.com/subject/ivnrjxtx.html