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

  • 2019 年 11 月 13 日
  • 筆記

不知道一些同學有沒有這種疑問,為什麼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來獲取的。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Tags: