在研究【自定義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
- 若是 無事務 || 事務是PROPAGATION_NOT_SUPPORTED 時,是包含開啓鏈接的時間的。
- 在事務PROPAGATION_REQUIRED (相似的狀況還有PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED )時, 將在切面處理TransactionAspectSupport.invokeWithinTransaction方法在處理真實調用前提早開啓!
在Spring中事務切面都是在service層上,此時須要共享一個sqlSession及connect!(最終的commit是由connect.commit)spa
- 在本測試中:
- 事務傳播入PROPAGATION_REQUIRED : 複用。
- 事務傳播入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
- 對於每個Mapper都會經過MapperFactoryBean來生成一個handler是MapperProxy的代理對象。
- 在dao調用方法時,轉到MapperProxy調用MapperMethod來選擇調用方法,再用 SqlSessionTemplate實例中的sqlSessionProxy來執行。會進入sqlSessionProxy的處理類: SqlSessionInterceptor ;
- SqlSessionInterceptor在開啓sqlSession<DefaultSqlSession>的過程當中:經過SpringManagedTransactionFactory構建的SpringManagedTransaction置入具體執行的executor,此時在Transaction中Connection是null;經過Plugin來織入executor對interceptor的代理;最終返回DefaultSqlSession實例。
- 接着執行DefaultSqlSession的dml方法其實是執行executor的dml方法,此時executor是代理類,優先執行代理的切面方法<interceptor>, 後續執行SimpleExecutor。
- SimpleExecutor在doUpdate、doQuery中,會構建PreparedStatement,此時要獲取Connection。經過Executor中存入的SpringManagedTransaction獲取鏈接時,將斷定ConnectionHolder是否爲null(若是是事務則不會爲空),是則開啓一個新的數據庫鏈接。
- 最終是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 :
- 獲取配置的事務管理器 PlatformTransactionManager(實例DataSourceTransactionManager)、及joinpointIdentification,執行createTransactionIfNecessary方法。
- 在AbstractPlatformTransactionManager.getTransaction方法中調用DataSourceTransactionManager的doGetTransaction會斷定當前線程事務管理中是否有ConnectionHolder,有則放入新建的DataSourceTransactionObject對象中。接着斷定是否有歷史存在的事務信息(經過ConnectionHolder是否存活來斷定)
- 無歷史 & 當前PROPAGATION_REQUIRED
執行DataSourceTransactionManager.doBegin方法: 斷定這個ConnectionHolder是否爲空,若是爲空,生成了一個connection實例封裝在ConnectionHolder中,與DataSource 做爲值對綁定在當前線程上; 若是存在則複用。
- 有歷史 & 傳播
執行AbstractPlatformTransactionManager.handleExistingTransaction方法。 將當前的TransactionInfo綁定在當前線程上,oldTransactionInfo被保存在當前TransactionInfo實例中。
- 若 PROPAGATION_NOT_SUPPORTED : newSynchronization = true。
關鍵在於suspend方法!會調用TransactionSynchronizationManager.unbindResource清除線程上下文中原綁定的SessionHolder和connectionHolder
- PROPAGATION_REQUIRED : 事務上下文不會被清理, newSynchronization = false;
- 接着回到【常規流程的步驟2】, 在新建立DefaultSqlSession時會建立SqlSessionHolder綁定到線程上下文中, SqlSessionHolder.requested(): referenceCount++。 此時當SpringManagedTransaction真正須要獲取connection時,會從事務管理器的線程上下文中獲取到。
- 當有多個Mapper方法執行時,此時的sqlSession將從綁定在事務管理器線程上下文中的SqlSessionHolder中獲取。此時SpringManagedTransaction中的connection仍是提早開啓的那個鏈接
當事務的傳播是PROPAGATION_NOT_SUPPORTED時,在handleExistingTransaction將原事務suspend了,原有線程上下文中的sessionHolder與connectHolder都被清空了,因此會從新建立新的sqlSession及connect 。此事務單獨出來獨立執行,在執行完成時cleanupAfterCompletion將resume原事務。

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