mybatis源码 (五) —— mybatis的事务如何被spring管理

  • Post author:
  • Post category:其他


要想使用spring的事务,要加入mybatis-spring依赖包

        <!-- 引用插件依赖:MyBatis整合Spring -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
    <context:property-placeholder location="classpath:properties/*.properties" />
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
        destroy-method="close">
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="maxActive" value="10" />
        <property name="minIdle" value="5" />
    </bean>

    <!-- sqlsessionfactory -->
    <!-- 让spring管理sqlsessionfactory 使用mybatis和spring整合包中的 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 数据库连接池 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 加载mybatis的全局配置文件 -->
        <property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
    </bean>

    <!-- mapper扫描器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.taotao.mapper" />
    </bean>

配置sqlSessionFactory给spring来管理

SqlSessionFactoryBean这是一个FactoryBean相信读过spring源码的都知道

org.mybatis.spring.SqlSessionFactoryBean#getObject

  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      //sqlSessionFactory没有构建好,那么就调用afterPropertiesSet
      afterPropertiesSet();
    }
    //返回构建好的sqlSessionFactory
    return this.sqlSessionFactory;
  }

org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet

  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    .....
    //构建SqlSessionFactory
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    .....

    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }

    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

    ......

    return this.sqlSessionFactoryBuilder.build(configuration);
  }

这段代码可以看出transactionFactory 等于null的时候回去创建一个SpringMngTransFactory

并且把transFactory设置给Environment然后再把Environment设置给configuration,最后

sqlSessionFactoryBuilder.build(configuration);返回sqlSessionFactory


在DefaultSqlSessionFactory#openSessionFromConnection方法中创建了Transaction

  private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      boolean autoCommit;
      try {
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) {
        // Failover to true, as most poor drivers
        // or databases won't support transactions
        autoCommit = true;
      }      
      //从configuration中取出environment对象
      final Environment environment = configuration.getEnvironment();
      //从environment中取出TransactionFactory
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //创建Transaction
      final Transaction tx = transactionFactory.newTransaction(connection);
      //创建包含事务操作的执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      //构建包含执行器的SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

DefaultSqlSessionFactory#getTransactionFactoryFromEnvironment

  private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    //如果没有配置事务工厂,则返回托管事务工厂
    if (environment == null || environment.getTransactionFactory() == null) {
      return new ManagedTransactionFactory();
    }
    return environment.getTransactionFactory();
  }

这里返回SpringManagedTransactionFactory对象,然后调用

SpringManagedTransactionFactory#newTransaction(javax.sql.DataSource, org.apache.ibatis.session.TransactionIsolationLevel, boolean)

  @Override
  public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
    return new SpringManagedTransaction(dataSource);
  }

也就是说mybatis的执行事务的事务管理器就切换成了SpringManagedTransaction

我们再看org.mybatis.spring.transaction.SpringManagedTransaction#getConnection

  @Override
  public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      //获取connection
      openConnection();
    }
    return this.connection;
  }

  private void openConnection() throws SQLException {
    //调用DataSourceUtils 拿到数据库连接
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

  .....

org.springframework.jdbc.datasource.DataSourceUtils#getConnection

    public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
        try {
            return doGetConnection(dataSource);
        }
        catch (SQLException ex) {
            throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
        }
    }

此处进入spring-jdbc的模块代码,猜想:

  1. spring的@trans事务操作的conn会保存在一个ThreadLocal中
  2. 当mybatis操作数据库时从这个ThreadLocal中去取conn,这样就可以做到spring来控制mybatis的数据库操作了

下面我们就来验证我们的猜想:

org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection

    public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        Assert.notNull(dataSource, "No DataSource specified");
        //获取ConnectionHolder
        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        /**
         * conHolder不为空 有链接
         */
        if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
            conHolder.requested();

            //如果conHolder没有连接
            if (!conHolder.hasConnection()) {
                logger.debug("Fetching resumed JDBC Connection from DataSource");
                //取出一个连接设置给conHolder
                conHolder.setConnection(fetchConnection(dataSource));
            }
            //返回conHolder中的连接
            return conHolder.getConnection();
        }

        .....

想看怎么获取connHolder

org.springframework.transaction.support.TransactionSynchronizationManager#getResource

    @Nullable
    public static Object getResource(Object key) {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        //获取ConnectionHolder
        Object value = doGetResource(actualKey);
        ....
        return value;
    }
    //保存数据库连接的ThreadLocal
    private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<>("Transactional resources");

    @Nullable
    private static Object doGetResource(Object actualKey) {
        /**
         * 从threadlocal <Map<Object, Object>>中取出来当前线程绑定的map
         * map里面存的是<dataSource,ConnectionHolder>
         */
        Map<Object, Object> map = resources.get();
        if (map == null) {
            return null;
        }
        //map中取出来对应dataSource的ConnectionHolder
        Object value = map.get(actualKey);
        // Transparently remove ResourceHolder that was marked as void...
        if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
            map.remove(actualKey);
            // Remove entire ThreadLocal if empty...
            if (map.isEmpty()) {
                resources.remove();
            }
            value = null;
        }
        return value;
    }

可以看到确实是从ThreadLocal中取出来的conn,而spring自己的事务也是操作的这个ThreadLocal中的conn来进行事务的开启和回滚

然后就取出来connHolder中的conn返回,当取出来的conn为空时候,调用

org.springframework.jdbc.datasource.DataSourceUtils#fetchConnection

    private static Connection fetchConnection(DataSource dataSource) throws SQLException {
        //从数据源取出来conn
        Connection con = dataSource.getConnection();
        if (con == null) {
            throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
        }
        return con;
    }

然后把从数据源取出来的连接返回

到此mybatis的事务是怎么被spring管理的就显而易见了



版权声明:本文为u011702633原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。