標籤:

Mybatis源碼分析(二)--Sqlsession的執行流程

上一篇Mapper動態代理中發現Mybatis會對Mapper介面的方法轉向mapperMethod.execute(sqlSession, args),那麼該篇就學習Mybatis對於sql的執行總體流程,文章不會涉及很多細節點,重點學習其設計以及這樣做的理由.


SqlCommand

SqlCommandMapperMethod的一個內部類,其封裝著要執行sql的id(xml的namespace+方法名)與類型(select,insert等),這些都是從MappedStatement中獲取到,MappedStatement是mybatis初始化讀取xml時所構造的對象,具體可以參考之前的文章.對於一個確定的Mapper介面中方法來說這個是確定的值.還有這裡有些人認為是命令模式,我認為不是,這裡只是該方法對應sql的唯一標識的體現,從下面代碼Mybatis對其的使用來看,也不是命令模式具有的行為,而對於命令的執行實際上是sqlSession來執行的,而命令模式的要求是命令中封裝委託對象,調用其excute()把任務交給委託執行的對象.

Mybatis對sqlCommand的使用

public Object execute(SqlSession sqlSession, Object[] args) {n Object result;n switch (command.getType()) {n case INSERT: {n tObject param = method.convertArgsToSqlCommandParam(args);n result = rowCountResult(sqlSession.insert(command.getName(), param));n break;n }n case UPDATE: {n Object param = method.convertArgsToSqlCommandParam(args);n result = rowCountResult(sqlSession.update(command.getName(), param));n break;n }n ...............n

MethodSignature與ParamNameResolver

MethodSignature也是MapperMethod中的一個內部類對象,其封裝著該方法的詳細信息,比如返回值類型,參數值類型等,分析該類可以得到Mybatis支持的返回類型有集合,Map,游標等各式各樣,還支持自定義結果映射器.

ParamNameResolver是用於方法參數名稱解析並重命名的一個類,在Mybatis的xml中使用#{0},#{id}或者註解@Param()等寫法都是合法的,為什麼合法這個類就是解釋,具體的分析過程因為跨度比較長,後面專用一篇文章來分析.

INSERT,UPDATE,DELETE的結果處理

對於這三種方法的執行,Mybatis會用rowCountResult()方法包裹結果,從源碼中可以很清楚的看出來Mybatis只支持返回void,Integer,Long,Boolean類型的值,默認是int類型,這裡建議數量查詢使用int.

private Object rowCountResult(int rowCount) {n final Object result;n if (method.returnsVoid()) {n result = null;n } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {n result = rowCount;n } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {n result = (long)rowCount;n } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {n result = rowCount > 0;n } else {n throw new BindingException("Mapper method " + command.getName() + " has an unsupported return type: " + method.getReturnType());n }n return result;n}n

Select的處理

Select是最複雜的處理,其擁有多樣的返回值類型,從源碼中可以發現Mybatis支持自定義結果映射器,集合返回,Map返回,游標返回以及單條返回.具體該方法是屬於哪一種類型在MethodSignature中都有定義,這裡不多敘述.

case SELECT:n if (method.returnsVoid() && method.hasResultHandler()) {n executeWithResultHandler(sqlSession, args);n result = null;n } else if (method.returnsMany()) {n result = executeForMany(sqlSession, args);n } else if (method.returnsMap()) {n result = executeForMap(sqlSession, args);n } else if (method.returnsCursor()) {n result = executeForCursor(sqlSession, args);n } else {n Object param = method.convertArgsToSqlCommandParam(args);n result = sqlSession.selectOne(command.getName(), param);n }n break;n

SqlSession

上述流程之後,SQL的執行就轉交給SqlSession,這裡會設置參數,去資料庫查詢,映射結果,可謂是Mybatis的核心.SqlSession下有如下四大對象.

  1. ParameterHandler: 處理參數設置問題
  2. ResultHandler: 結果處理
  3. StatementHandler: 負責連接資料庫,執行sql
  4. Executor: 對上述過程的調度組織.

Executor的橋接設計模式

Exexutor是一種一對多的模式,所謂的一是對於調用方Client,其任務就是調度執行sql,獲取結果返回,所謂的多是其實現可以有多種,比如Mybatis的SimpleExecutor,ReuseExecutor,BatchExecutor其實現這個功能的方式都有些差異,Mybatis在這裡的實現就是採用了橋接設計模式,具體結構如下圖.

關於橋接模式或者其他設計模式可以參考此鏈接design_patterns

因為對於調用方來說只關心介面,因此這裡提供Exexutor介面,對於內部多種實現把公共的部分比如緩存處理提取出來作為抽象類BaseExecutor,抽象類具有抽象方法與具體方法,那麼最適合作為執行模板.其不同的內容定義為抽象方法,由其子類SimpleExecutor,ReuseExecutor等來實現.這一分支可以視作模板設計模式.

另外是一個CachingExecutor,Mybatis會在xml中根據配置cacheEnabled來初始化該類,默認為true,那麼該類的作用也就是二級緩存.作為橋接類,其對其他的Executor進行調度,並緩存其結果,解耦介面與實現類之間的聯繫.

那麼問題就來了

  • 橋接模式是怎麼體現的?橋接模式主要是針對介面與實現類的橋接,按理說介面與實現類是屬於強耦合的關係,那麼使用橋接模式的話就可以去除這種耦合,橋接類中隨時可以更換該介面的實現類,比如這裡的CacheingExecutor其本身目的是二級緩存,但是二級緩存是針對Executor下的多個實現類,那麼這裡做下橋接則是一種很優雅的解決方式.
  • 二級緩存為什麼不用插件形式來實現,反而用橋接模式來實現呢?這個問題估計需要我分析完插件設計後才能回答,也可能是其本身設計的問題.占坑.
  • BaseExecutor是一種怎樣的設計?這裡是很明顯的模板設計,那麼這裡就要談對抽象類以及介面的理解,個人認為抽象類與介面的不同之處在於介面定義的是協議,一般對外使用,抽象類定義的是過程,也就是模板,這也是抽象類中非抽象方法與抽象方法共存的優勢.

除去上述問題,接下來的執行流程是很清晰的

BaseExecutor

@Overriden public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {n BoundSql boundSql = ms.getBoundSql(parameter);//獲取sql,此時還都是?佔位符狀態的sql n CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); //獲取緩存key,根據id,sql,分頁參數計算n return query(ms, parameter, rowBounds, resultHandler, key, boundSql);//跳到下面方法執行n }n @SuppressWarnings("unchecked")n @Overriden public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {n ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());n if (closed) {n throw new ExecutorException("Executor was closed.");n }n //queryStack用於延時載入,暫時未研究,若配置不用緩存,則每次查詢前清空一級緩存.n if (queryStack == 0 && ms.isFlushCacheRequired()) {n clearLocalCache();n }n List<E> list;n try {n queryStack++;n //緩存中取出數據,具體會在緩存詳解中分析,這裡只需要了解具體執行過程n list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;n if (list != null) {n //針對存儲過程更新參數緩存n handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);n } else {n //緩存未中則去查資料庫n list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);n }n } finally {n queryStack--;n }n //這邊是延遲載入的實現,不在本次分析內容中n if (queryStack == 0) {n for (DeferredLoad deferredLoad : deferredLoads) {n deferredLoad.load();n }n // issue #601n deferredLoads.clear();n if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {n // issue #482n clearLocalCache();n }n }n return list;n }n

接下來是DB的查詢,DB的查詢主要由其子類來實現.

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {n List<E> list;n //這裡先放入緩存中佔位符,一級緩存的實現n localCache.putObject(key, EXECUTION_PLACEHOLDER);n try {n //調用子類的方法處理,模板方法的體現n list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);n } finally {n localCache.removeObject(key);n }n //放入查詢結果緩存,一級緩存的實現n localCache.putObject(key, list);n //存儲過程還需要緩存參數n if (ms.getStatementType() == StatementType.CALLABLE) {n localOutputParameterCache.putObject(key, parameter);n }n return list;n}n

SimpleExecutor

按照上述模板的執行,SimpleExecutor是真正從資料庫查詢的地方,這裡也能看出來模板設計模式的好處,將緩存處理與實際數據查詢分離解耦,各司其職.

查詢是要經過StatementHandler組織ParameterHandler,ResultHandler的處理過程,那麼StatementHandler承擔了什麼樣的角色?

@Overridenpublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {n Statement stmt = null;n try {n Configuration configuration = ms.getConfiguration();n //創建statementn StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);n //獲取連接,設置參數等預處理n stmt = prepareStatement(handler, ms.getStatementLog());n //執行查詢並映射結果n return handler.<E>query(stmt, resultHandler);n } finally {n closeStatement(stmt);n }n}n

篇幅已經過長了,剩下的StatementHandler等也是類似的設計,不打算再繼續分析了,有興趣的同學可以自己研究一下.接下來會對一些關鍵點的實現分析,比如sql的解析,延遲載入的實現等.

推薦閱讀

  • Mybatis源碼分析(一)--Mapper的動態代理
  • Mybatis源碼分析(三)--動態Sql中的參數解析

原文作者:茶飲

原文地址:Mybatis源碼分析(二)--Sqlsession的執行流程

版權說明:本文來源於極樂科技合作作者,版權歸作者所有。

一元搶購微信小程序>>>鏈接地址

推薦閱讀:

Apache Pulsar的多租戶消息系統
前端必備技能——本地伺服器的搭建&配置
清新脫俗的 Web 伺服器 Caddy
React 路/粉/黑 都該了解的 React license 爭議
python如何抓取本地數據包?

TAG:MyBatis | SQL | Apache |