mybatis(8) - spring事務下mybatis的執行過程

在研究【自定義sql的Executor攔截plugin來統計sql執行耗時是否包括真實與db進行IO鏈接獲取connect】時, 發現事情遠沒有想象中的簡單!spring

前言

Spring如何事務切面代碼入口見: @Transactional (TransactionInterceptor)處理 TransactionAspectSupport.invokeWithinTransaction  ;  sql

具體處理事務詳見:AbstractPlatformTransactionManager.getTransaction 方法中對 doBegin(開啓鏈接)、handleExistingTransaction (處理事務傳播)兩方法的調用。數據庫

同時: 因使用TDDL分庫分表組件的數據源TDataSource,會致使與直接使用DruidDataSource會在真實與db進行IO獲取鏈接connect的時機上不一樣! session

詳見:mybatis(4) - TDDL下的getConnection的處理不一樣mybatis

因此:【sql攔截器統計耗時含數據庫鏈接時間】是片面的。app

該如何解釋這句話呢?框架

本文以PROPAGATION_REQUIRED 傳播入 PROPAGATION_NOT_SUPPORTED 爲例。測試

在直接使用DruidDataSource的狀況下:ui

  1. 若是  無事務 ||  事務是PROPAGATION_NOT_SUPPORTED  時,是包含開啓鏈接的時間的。
  2. 在事務PROPAGATION_REQUIRED  (相似的狀況還有PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED )時, 將在切面處理TransactionAspectSupport.invokeWithinTransaction方法在處理真實調用前提早開啓!

    在Spring中事務切面都是在service層上,此時須要共享一個sqlSession及connect(最終的commit是由connect.commit)spa

  3. 在本測試中: 
    1. 事務傳播入PROPAGATION_REQUIRED :  複用。
    2. 事務傳播入PROPAGATION_NOT_SUPPORTED :  老的事務上下文被suspend,事務管理器內線程上下文綁定的sqlSessionHolder與connectionHolder被清空;這樣在SqlSessionInterceptor執行時又會從新建立sqlSession及connection。

而在使用TDataSource分庫分表的狀況下: 都是含有真實開啓數據庫鏈接時間的。常規流程執行到最後的PreparedStatement.execute(),再跳入框架內部處理肯定具體的DruidDataSource實例來開啓真實鏈接!

常規流程

先開啓sqlSession, 再開啓connection;
執行完成後: 先在SqlSession的commit中調用connection進行commit, 再在關閉sqlSession 過程當中 調用關閉connection;

  • Configuration 從mapperScan時就一直向下傳遞,各個重要的執行類中都起很重要的做用!!
  • SqlSessionInterceptor 管控着SqlSession及connection的開啓和關閉,以單個Mapper的單個方法的執行過程爲一個生命週期。sqlSession在close以前自動commit(ture) (實際調用到是connection.commit);

啓動執行

Mapper.insert -> MapperProxy.invoke -> MapperMethod.execute -> SqlSessionTemplate.insert 
->  SqlSessionInterceptor.invoke  -> DefaultSqlSession.insert ->BaseExecutor.update -> SimpleExecutor.doUpdate -> StatementHandler.update -> PreparedStatement.execute

  1. 對於每個Mapper都會經過MapperFactoryBean來生成一個handler是MapperProxy的代理對象。
  2. 在dao調用方法時,轉到MapperProxy調用MapperMethod來選擇調用方法,再用 SqlSessionTemplate實例中的sqlSessionProxy來執行。會進入sqlSessionProxy的處理類: SqlSessionInterceptor ;
  3. SqlSessionInterceptor在開啓sqlSession<DefaultSqlSession>的過程當中:經過SpringManagedTransactionFactory構建的SpringManagedTransaction置入具體執行的executor,此時在Transaction中Connection是null;經過Plugin來織入executor對interceptor的代理;最終返回DefaultSqlSession實例。
  4. 接着執行DefaultSqlSession的dml方法其實是執行executor的dml方法,此時executor是代理類,優先執行代理的切面方法<interceptor>, 後續執行SimpleExecutor。
  5. SimpleExecutor在doUpdate、doQuery中,會構建PreparedStatement,此時要獲取Connection。經過Executor中存入的SpringManagedTransaction獲取鏈接時,將斷定ConnectionHolder是否爲null(若是是事務則不會爲空),是則開啓一個新的數據庫鏈接。
  6. 最終是PreparedStatement的具體執行Dml。

執行結束

先調用connect.commit,再關閉qlSession,直接就調用connection的關閉。
SqlSessionInterceptor內 defalutSqlSession.commit(true) 提交sqlSession(connection.commit) -> SqlSessionUtils.closeSqlSession -> DefaultSqlSession.close->CachingExecutor.close->SimpleExecutor.close-> BaseExecutor.close -> SpringManagedTransaction.close ->DataSourceUtils.doCloseConnection

帶事務的執行過程

 切面處理類中開啓connection,再SqlSessionInterceptor中開啓sqlSession; 再複用connection及sqlSession(發生事務傳播,不限於PROPAGATION_NOT_SUPPORTED 將從新建立新對象); 
執行完成後:SqlSessionInterceptor內只是SqlSessionHolder計數減一; 再到切面處理類中先commit掉sqlSession,再關閉sqlSession; 再commit掉connection,最後關閉connection;

  • TransactionSynchronizationManager提供了線程上下文綁定(SqlSessionHolder, SqlSessionFactory) 與 (ConnectionHolder, dataSource) 的方法;
    DataSourceTransactionManager extends AbstractPlatformTransactionManager提供了事務的控制管理。
  • SqlSessionHolder 記錄每個Mapper方法對於sqlSession的引用(每一個mapper方法執行時,ref++; closeSession時,ref --);
  • ConnectionHolder 記錄一次完整事務對connection的引用 (在首次PreparedStatement時使用,ref++;在事務切面處理類後置處理內 ref--)。在PROPAGATION_REQUIRED事務執行是線程共享的,以一次徹底處理事務做爲一個生命週期!! (真實在SqlSessionInterceptor.getSqlSession時,拿到的SqlSessionHolder中: SqlSession裏SpringManagedTransaction返回的connection是那個提早開啓的鏈接)

 spring事務在切面處理類中提早開啓connect

@Transactional (TransactionInterceptor)處理 TransactionAspectSupport.invokeWithinTransaction  :

  1. 獲取配置的事務管理器 PlatformTransactionManager(實例DataSourceTransactionManager)、及joinpointIdentification,執行createTransactionIfNecessary方法。
  2. 在AbstractPlatformTransactionManager.getTransaction方法中調用DataSourceTransactionManager的doGetTransaction會斷定當前線程事務管理中是否有ConnectionHolder,有則放入新建的DataSourceTransactionObject對象中。接着斷定是否有歷史存在的事務信息(經過ConnectionHolder是否存活來斷定)
    1. 無歷史 & 當前PROPAGATION_REQUIRED
       執行DataSourceTransactionManager.doBegin方法: 斷定這個ConnectionHolder是否爲空,若是爲空,生成了一個connection實例封裝在ConnectionHolder中,與DataSource 做爲值對綁定在當前線程上; 若是存在則複用。
    2. 有歷史  & 傳播
      執行AbstractPlatformTransactionManager.handleExistingTransaction方法。 將當前的TransactionInfo綁定在當前線程上,oldTransactionInfo被保存在當前TransactionInfo實例中。
      1. 若 PROPAGATION_NOT_SUPPORTED :  newSynchronization = true
        關鍵在於suspend方法!會調用TransactionSynchronizationManager.unbindResource清除線程上下文中原綁定的SessionHolder和connectionHolder
      2. PROPAGATION_REQUIRED :  事務上下文不會被清理, newSynchronization = false;
  3. 接着回到【常規流程的步驟2】, 在新建立DefaultSqlSession時會建立SqlSessionHolder綁定到線程上下文中, SqlSessionHolder.requested():  referenceCount++。   此時當SpringManagedTransaction真正須要獲取connection時,會從事務管理器的線程上下文中獲取到。
  4.  當有多個Mapper方法執行時,此時的sqlSession將從綁定在事務管理器線程上下文中的SqlSessionHolder中獲取。此時SpringManagedTransaction中的connection仍是提早開啓的那個鏈接

當事務的傳播是PROPAGATION_NOT_SUPPORTED時,在handleExistingTransaction將原事務suspend了,原有線程上下文中的sessionHolder與connectHolder都被清空了,因此會從新建立新的sqlSession及connect 。此事務單獨出來獨立執行,在執行完成時cleanupAfterCompletion將resume原事務。

 執行結束

 區別

  1. SqlSessionInterceptor再也不執行sqlSession.commit(true) 語句。  (常規是: 最終調用connect.commit 直接提交了鏈接
  2. 執行closeSqlSession方法時: 只執行到了SqlSessionHolder.released(): referenceCount--。 (常規是:connect.close , 直接關閉了鏈接

執行過程最終回到TransactionAspectSupport執行commitTransactionAfterReturning方法:

  1. 執行AbstractPlatformTransactionManager的processCommit()
    1. 調用triggerBeforeCommit方法:會調用SqlSessionUtils的beforeCommit:  DefaultSqlSession.commit(false);(只處理了sqlSession的內容,並無提交connect)
    2. 調用triggerBeforeCompletion方法: 會調用SqlSessionUtils的beforeCompletion: DefaultSqlSession.close,  但最終只是 ConnectionHolder.released():referenceCount--  (常規流程是直接關閉了connect)
    3. 調用doCommit方法: 接着執行DataSourceTransactionManager的doCommit時:Connection.commit();
    4. 調用cleanupAfterCompletion方法: 最終在執行AbstractPlatformTransactionManager(實例爲DataSourceTransactionManager)的cleanupAfterCompletion時調用DataSourceUtils.releaseConnection 再真實關閉鏈接。
  1. DefaultTransactionStatus 有2個重要的成員變量: newSynchronization & newTransaction
    • newSynchronization = false 致使在triggerBeforeCommit、triggerBeforeCompletion、triggerAfterCommit等都不會真正的執行! 而對於newTransaction 而言, 只有在首次進入 REQUIRED、REQUIRES_NEW、NESTED 事務 || 傳播爲REQUIRES_NEW 才爲 true。   
    • newTransaction  = false 致使doCleanupAfterCompletion不會執行, doRollbackOnCommitException不執行回滾、processCommit的doCommit不執行等等
相關文章
相關標籤/搜索