1. 程式人生 > >mybatis原始碼 (五) —— mybatis的事務如何被spring管理

mybatis原始碼 (五) —— mybatis的事務如何被spring管理

要想使用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管理的就顯而易見了