1. 程式人生 > >Mybaits 原始碼解析 (十二)----- Mybatis的事務如何被Spring管理?Mybatis和Spring事務中用的Connection是同一個嗎?

Mybaits 原始碼解析 (十二)----- Mybatis的事務如何被Spring管理?Mybatis和Spring事務中用的Connection是同一個嗎?

不知道一些同學有沒有這種疑問,為什麼Mybtis中要配置dataSource,Spring的事務中也要配置dataSource?那麼Mybatis和Spring事務中用的Connection是同一個嗎?我們常用配置如下

<!--會話工廠 -->
<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" />

看到沒,sqlSessionFactory中配置了dataSource,transactionManager也配置了dataSource,我們來回憶一下SqlSessionFactoryBean這個類

 1 protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
 2 
 3     // 配置類
 4    Configuration configuration;
 5     // 解析mybatis-Config.xml檔案,
 6     // 將相關配置資訊儲存到configuration
 7    XMLConfigBuilder xmlConfigBuilder = null;
 8    if (this.configuration != null) {
 9      configuration = this.configuration;
10      if (configuration.getVariables() == null) {
11        configuration.setVariables(this.configurationProperties);
12      } else if (this.configurationProperties != null) {
13        configuration.getVariables().putAll(this.configurationProperties);
14      }
15     //資原始檔不為空
16    } else if (this.configLocation != null) {
17      //根據configLocation建立xmlConfigBuilder,XMLConfigBuilder構造器中會建立Configuration物件
18      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
19      //將XMLConfigBuilder構造器中建立的Configuration物件直接賦值給configuration屬性
20      configuration = xmlConfigBuilder.getConfiguration();
21    } 
22    
23     //略....
24 
25    if (xmlConfigBuilder != null) {
26      try {
27        //解析mybatis-Config.xml檔案,並將相關配置資訊儲存到configuration
28        xmlConfigBuilder.parse();
29        if (LOGGER.isDebugEnabled()) {
30          LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
31        }
32      } catch (Exception ex) {
33        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
34      }
35    }
36     
37    if (this.transactionFactory == null) {
38      //事務預設採用SpringManagedTransaction,這一塊非常重要
39      this.transactionFactory = new SpringManagedTransactionFactory();
40    }
41     // 為sqlSessionFactory繫結事務管理器和資料來源
42     // 這樣sqlSessionFactory在建立sqlSession的時候可以通過該事務管理器獲取jdbc連線,從而執行SQL
43    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
44     // 解析mapper.xml
45    if (!isEmpty(this.mapperLocations)) {
46      for (Resource mapperLocation : this.mapperLocations) {
47        if (mapperLocation == null) {
48          continue;
49        }
50        try {
51          // 解析mapper.xml檔案,並註冊到configuration物件的mapperRegistry
52          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
53              configuration, mapperLocation.toString(), configuration.getSqlFragments());
54          xmlMapperBuilder.parse();
55        } catch (Exception e) {
56          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
57        } finally {
58          ErrorContext.instance().reset();
59        }
60 
61        if (LOGGER.isDebugEnabled()) {
62          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
63        }
64      }
65    } else {
66      if (LOGGER.isDebugEnabled()) {
67        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
68      }
69    }
70 
71     // 將Configuration物件例項作為引數,
72     // 呼叫sqlSessionFactoryBuilder建立sqlSessionFactory物件例項
73    return this.sqlSessionFactoryBuilder.build(configuration);
74 }

我們看第39行,Mybatis整合Spring後,預設使用的transactionFactory是SpringManagedTransactionFactory,那我們就來看看其獲取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();
    }
}

private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
      return new ManagedTransactionFactory();
    }
    //這裡返回SpringManagedTransactionFactory
    return environment.getTransactionFactory();
}

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

SpringManagedTransaction

也就是說mybatis的執行事務的事務管理器就切換成了SpringManagedTransaction,下面我們再去看看SpringManagedTransactionFactory類的原始碼:

public class SpringManagedTransaction implements Transaction {
    private static final Log LOGGER = LogFactory.getLog(SpringManagedTransaction.class);
    private final DataSource dataSource;
    private Connection connection;
    private boolean isConnectionTransactional;
    private boolean autoCommit;

    public SpringManagedTransaction(DataSource dataSource) {
        Assert.notNull(dataSource, "No DataSource specified");
        this.dataSource = dataSource;
    }

    public Connection getConnection() throws SQLException {
        if (this.connection == null) {
            this.openConnection();
        }

        return this.connection;
    }

    private void openConnection() throws SQLException {
        //通過DataSourceUtils獲取connection,這裡和JdbcTransaction不一樣
        this.connection = DataSourceUtils.getConnection(this.dataSource);
        this.autoCommit = this.connection.getAutoCommit();
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
        }

    }

    public void commit() throws SQLException {
        if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Committing JDBC Connection [" + this.connection + "]");
            }
            //通過connection提交,這裡和JdbcTransaction一樣
            this.connection.commit();
        }

    }

    public void rollback() throws SQLException {
        if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]");
            }
            //通過connection回滾,這裡和JdbcTransaction一樣
            this.connection.rollback();
        }

    }

    public void close() throws SQLException {
        DataSourceUtils.releaseConnection(this.connection, this.dataSource);
    }

    public Integer getTimeout() throws SQLException {
        ConnectionHolder holder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource);
        return holder != null && holder.hasTimeout() ? holder.getTimeToLiveInSeconds() : null;
    }
}

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);
    }
}

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
    Assert.notNull(dataSource, "No DataSource specified");
    //TransactionSynchronizationManager重點!!!有沒有很熟悉的感覺??
    //還記得我們前面Spring事務原始碼的分析嗎?@Transaction會建立Connection,並放入ThreadLocal中
    //這裡從ThreadLocal中獲取ConnectionHolder
    ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
    if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) {
        logger.debug("Fetching JDBC Connection from DataSource");
        //如果沒有使用@Transaction,那呼叫Mapper介面方法時,也是通過Spring的方法獲取Connection
        Connection con = fetchConnection(dataSource);
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            logger.debug("Registering transaction synchronization for JDBC Connection");
            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) {
                //將獲取到的ConnectionHolder放入ThreadLocal中,那麼當前執行緒呼叫下一個介面,下一個介面使用了Spring事務,那Spring事務也可以直接取到Mybatis建立的Connection
                //通過ThreadLocal保證了同一執行緒中Spring事務使用的Connection和Mapper代理類使用的Connection是同一個
                TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
            }
        }

        return con;
    } else {
        conHolder.requested();
        if (!conHolder.hasConnection()) {
            logger.debug("Fetching resumed JDBC Connection from DataSource");
            conHolder.setConnection(fetchConnection(dataSource));
        }

        //所以如果我們業務程式碼使用了@Transaction註解,在Spring中就已經通過dataSource建立了一個Connection並放入ThreadLocal中
        //那麼當Mapper代理物件呼叫方法時,通過SqlSession的SpringManagedTransaction獲取連線時,就直接獲取到了當前執行緒中Spring事務建立的Connection並返回
        return conHolder.getConnection();
    }
}

想看怎麼獲取connHolder 

org.springframework.transaction.support.TransactionSynchronizationManager#getResource

//儲存資料庫連線的ThreadLocal
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
@Nullable
public static Object getResource(Object key) {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    //獲取ConnectionHolder
    Object value = doGetResource(actualKey);
    ....
    return value;
}

@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來進行事務的開啟和回滾,由此我們知道了在同一執行緒中Spring事務中的Connection和Mybaits中Mapper代理物件中操作資料庫的Connection是同一個,當取出來的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;
}

我們再來回顧一下上篇文章中的SqlSessionInterceptor

 1 private class SqlSessionInterceptor implements InvocationHandler {
 2     private SqlSessionInterceptor() {
 3     }
 4 
 5     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 6         SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
 7 
 8         Object unwrapped;
 9         try {
10             Object result = method.invoke(sqlSession, args);
11             // 如果當前操作沒有在一個Spring事務中,則手動commit一下
12             // 如果當前業務沒有使用@Transation,那麼每次執行了Mapper介面的方法直接commit
13             // 還記得我們前面講的Mybatis的一級快取嗎,這裡一級快取不能起作用了,因為每執行一個Mapper的方法,sqlSession都提交了
14             // sqlSession提交,會清空一級快取
15             if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
16                 sqlSession.commit(true);
17             }
18 
19             unwrapped = result;
20         } catch (Throwable var11) {
21             unwrapped = ExceptionUtil.unwrapThrowable(var11);
22             if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
23                 SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
24                 sqlSession = null;
25                 Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
26                 if (translated != null) {
27                     unwrapped = translated;
28                 }
29             }
30 
31             throw (Throwable)unwrapped;
32         } finally {
33             if (sqlSession != null) {
34                 SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
35             }
36 
37         }
38         return unwrapped;
39     }
40 }

看第15和16行,如果我們沒有使用@Transation,Mapper方法執行完後,sqlSession將會提交,也就是說通過org.springframework.jdbc.datasource.DataSourceUtils#fetchConnection獲取到的Connection將會commit,相當於Connection是自動提交的,也就是說如果不使用@Transation,Mybatis將沒有事務可言。

如果使用了@Transation呢?那在呼叫Mapper代理類的方法之前就已經通過Spring的事務生成了Connection並放入ThreadLocal,並且設定事務不自動提交,當前執行緒多個Mapper代理物件呼叫資料庫操作方法時,將從ThreadLocal獲取Spring建立的connection,在所有的Mapper方法呼叫完後,Spring事務提交或者回滾,到此mybatis的事務是怎麼被spring管理的就顯而易見了

還有文章開頭的問題,為什麼Mybtis中要配置dataSource,Spring的事務中也要配置dataSource?

因為Spring事務在沒呼叫Mapper方法之前就需要開一個Connection,並設定事務不自動提交,那麼transactionManager中自然要配置dataSource。那如果我們的Service沒有用到Spring事務呢,難道就不需要獲取資料庫連線了嗎?當然不是,此時通過SpringManagedTransaction呼叫org.springframework.jdbc.datasource.DataSourceUtils#getConnection#fetchConnection方法獲取,並將dataSource作為引數傳進去,實際上獲取的Connection都是通過dataSource來獲取的。