【MyBatis源碼分析】select源碼分析及小結

来源:http://www.cnblogs.com/xrq730/archive/2017/06/11/6955491.html
-Advertisement-
Play Games

示例代碼 之前的文章說過,對於MyBatis來說insert、update、delete是一組的,因為對於MyBatis來說它們都是update;select是一組的,因為對於MyBatis來說它就是select。 本文研究一下select的實現流程,示例代碼為: selectMailById方法的 ...


示例代碼

之前的文章說過,對於MyBatis來說insert、update、delete是一組的,因為對於MyBatis來說它們都是update;select是一組的,因為對於MyBatis來說它就是select。

本文研究一下select的實現流程,示例代碼為:

 1 public void testSelectOne() {
 2     System.out.println(mailDao.selectMailById(8));
 3 }

selectMailById方法的實現為:

1 public Mail selectMailById(long id) {
2     SqlSession ss = ssf.openSession();
3     try {
4         return ss.selectOne(NAME_SPACE + "selectMailById", id);
5     } finally {
6         ss.close();
7     }
8 }

我們知道MyBatis提供的select有selectList和selectOne兩個方法,但是本文只分析且只需要分析selectOne方法,原因後面說。

 

selectOne方法流程

先看一下SqlSession的selectOne方法流程,方法位於DefaultSqlSession中:

 1 public <T> T selectOne(String statement, Object parameter) {
 2     // Popular vote was to return null on 0 results and throw exception on too many.
 3     List<T> list = this.<T>selectList(statement, parameter);
 4     if (list.size() == 1) {
 5       return list.get(0);
 6     } else if (list.size() > 1) {
 7       throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
 8     } else {
 9       return null;
10     }
11 }

這裡就是為什麼我說selectOne與selectList兩個方法只需要分析selectList方法就可以了的原因,因為在MyBatis中所有selectOne操作最後都會轉換為selectList操作,無非就是操作完畢之後判斷一下結果集的個數,如果結果集個數超過一個就報錯。

接著看下第3行的selectList的代碼實現,方法同樣位於DefaultSqlSession中:

 1 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
 2     try {
 3       MappedStatement ms = configuration.getMappedStatement(statement);
 4       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
 5     } catch (Exception e) {
 6       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
 7     } finally {
 8       ErrorContext.instance().reset();
 9     }
10 }

第3行獲取MappedStatement就不說了,跟一下第4行Executor的query方法實現,這裡使用了一個裝飾器模式,給SimpleExecutor加上了緩存功能,代碼位於CachingExecutor中:

1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
2     BoundSql boundSql = ms.getBoundSql(parameterObject);
3     CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
4     return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
5 }

第2行的代碼獲取BoundSql,BoundSql中的內容上文已經說過了,最後也會有總結。

第3行的代碼根據輸入參數構建緩存Key。

第4行的代碼執行查詢操作,看下代碼實現,代碼同樣位於CachingExecutor中:

 1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
 2       throws SQLException {
 3     Cache cache = ms.getCache();
 4     if (cache != null) {
 5       flushCacheIfRequired(ms);
 6       if (ms.isUseCache() && resultHandler == null) {
 7         ensureNoOutParams(ms, parameterObject, boundSql);
 8         @SuppressWarnings("unchecked")
 9         List<E> list = (List<E>) tcm.getObject(cache, key);
10         if (list == null) {
11           list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
12           tcm.putObject(cache, key, list); // issue #578 and #116
13         }
14         return list;
15       }
16     }
17     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
18 }

這裡並沒有配置且引用Cache,因此不執行第4行的判斷,執行第17行的代碼,代碼位於SimpleExecutor的父類BaseExecutor中,源碼實現為:

 1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 2     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
 3     if (closed) {
 4       throw new ExecutorException("Executor was closed.");
 5     }
 6     if (queryStack == 0 && ms.isFlushCacheRequired()) {
 7       clearLocalCache();
 8     }
 9     List<E> list;
10     try {
11       queryStack++;
12       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
13       if (list != null) {
14         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
15       } else {
16         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
17       }
18     } finally {
19       queryStack--;
20     }
21     if (queryStack == 0) {
22       for (DeferredLoad deferredLoad : deferredLoads) {
23         deferredLoad.load();
24       }
25       // issue #601
26       deferredLoads.clear();
27       if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
28         // issue #482
29         clearLocalCache();
30       }
31     }
32     return list;
33 }

這裡執行第16行的代碼,queryFromDatabase方法實現為:

 1 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 2     List<E> list;
 3     localCache.putObject(key, EXECUTION_PLACEHOLDER);
 4     try {
 5       list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
 6     } finally {
 7       localCache.removeObject(key);
 8     }
 9     localCache.putObject(key, list);
10     if (ms.getStatementType() == StatementType.CALLABLE) {
11       localOutputParameterCache.putObject(key, parameter);
12     }
13     return list;
14 }

代碼走到第5行,最終執行duQuery方法,方法的實現為:

 1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 2     Statement stmt = null;
 3     try {
 4       Configuration configuration = ms.getConfiguration();
 5       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 6       stmt = prepareStatement(handler, ms.getStatementLog());
 7       return handler.<E>query(stmt, resultHandler);
 8     } finally {
 9       closeStatement(stmt);
10     }
11 }

看到第4行~第6行的代碼都和前文update是一樣的,就不說了,handler有印象的朋友應該記得是PreparedStatementHandler,下一部分就分析一下和update的區別,PreparedStatementHandler的query方法是如何實現的。

 

PreparedStatementHandler的query方法實現

跟一下PreparedStatementHandler的query方法跟到底,其最終實現為:

1 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
2     PreparedStatement ps = (PreparedStatement) statement;
3     ps.execute();
4     return resultSetHandler.<E> handleResultSets(ps);
5 }

看到第3行執行查詢操作,第4行的代碼處理結果集,將結果集轉換為List,handleResultSets方法實現為:

 1 public List<Object> handleResultSets(Statement stmt) throws SQLException {
 2     ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
 3 
 4     final List<Object> multipleResults = new ArrayList<Object>();
 5 
 6     int resultSetCount = 0;
 7     ResultSetWrapper rsw = getFirstResultSet(stmt);
 8 
 9     List<ResultMap> resultMaps = mappedStatement.getResultMaps();
10     int resultMapCount = resultMaps.size();
11     validateResultMapsCount(rsw, resultMapCount);
12     while (rsw != null && resultMapCount > resultSetCount) {
13       ResultMap resultMap = resultMaps.get(resultSetCount);
14       handleResultSet(rsw, resultMap, multipleResults, null);
15       rsw = getNextResultSet(stmt);
16       cleanUpAfterHandlingResultSet();
17       resultSetCount++;
18     }
19 
20     String[] resultSets = mappedStatement.getResultSets();
21     if (resultSets != null) {
22       while (rsw != null && resultSetCount < resultSets.length) {
23         ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
24         if (parentMapping != null) {
25           String nestedResultMapId = parentMapping.getNestedResultMapId();
26           ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
27           handleResultSet(rsw, resultMap, null, parentMapping);
28         }
29         rsw = getNextResultSet(stmt);
30         cleanUpAfterHandlingResultSet();
31         resultSetCount++;
32       }
33     }
34 
35     return collapseSingleResultList(multipleResults);
36 }

總結一下這個方法。

第7行代碼,通過PreparedStatement的getResultSet方法獲取ResultSet,並將ResultSet包裝為ResultSetWrapper,ResultSetWrapper除了包含了ResultSet之外,還依次定義了資料庫返回的每條數據的每行列名、列對應的JDBC類型、列對應的Java Class的類型,除此之外最主要的是還包含了TypeHandlerRegister(類型處理器,所有的參數都是通過TypeHandler進行設置的)。

第9行代碼,獲取該<select>標簽中定義的ResultMap,不過這裡我有點沒弄明白,一個<select>標簽按道理應該只能定義一個resultMap屬性,但是這裡卻獲取的是一個List<ResultMap>,不是很清楚。

第11行代碼,做了一個校驗,即如果select出來有結果返回,但是沒有ResultMap或者ResultType與之對應的話,拋出異常,道理很簡單,沒有這2者之一,MyBatis並不知道將返迴轉成什麼樣子。

第12行~第18行的代碼,將ResultSetWrapper中的值根據ResultMap,轉成Java對象,先存儲在multipleResults中,這是一個List<Object>。

第20行~第33行的代碼,是用於處理<select>中定義的resultSets的,由於這裡沒有定義,因此跳過。

第35行的代碼,將multipleResults,根據其size大小,如果size=1,獲取0號元素,強轉為List<Object>;如果size!=1,直接返回multipleResults。

總得來說這個方法,根據資料庫返回的結果,封裝為自定義的ResultMap的流程基本是沒問題的,只是這裡的一個問題是,為什麼要定義一個multipleResults,最後根據multipleResults的size來判斷並拆分最終的結果,還沒有完全搞懂,這部分還要留待後面的工作中隨著MyBatis應用的深入再去學習。

 

小結

前文已經對MyBatis配置文件載入、CRUD操作都進行了分析,就從我自己的感覺來說,對整個流程基本有數,但是很多地方感覺還是有些印象不深,最主要的就是從什麼地方獲取什麼數據,獲取的數據在什麼地方使用,因此這裡做一個總結加深印象,主要總結的是MyBatis中重點的類中持有哪些內容。

首先是SqlSessionFactory,預設使用的是DefaultSqlSessionFactory,我們使用它來每次打開一個SqlSession,SqlSessionFactory持有:

接著是Configuration,它是所有配置信息最終存儲的位置,其中大部分的屬性尤其是布爾型值都可以通過<settings>標簽進行配置,任何的操作(如打開一個SqlSession、執行增刪改查等)都要從Configuration中拿相關信息,Configuration持有的一些重要屬性有:

接著是Environment,它存儲的是配置的資料庫環境信息,可以指定多個,但是最終只能使用一個,Environment持有的一些重要屬性有:

接著是MappedStatement,一個MappedStatement對應mapper文件中的一個<insert>、<delete>、<update>、<select>,每次執行MyBatis操作的時候先獲取對應的MappedStatement,MappedStatement持有的一些重要屬性有:

接著是BoundSql,BoundSql中最重要存儲的就是當前要執行的SQL語句,其餘還有要設置的參數信息與參數對象,BoundSql持有的屬性有:

最後是ParameterMapping,ParameterMapping是待設置的參數映射,存儲了待設置的參數的相關信息,ParameterMapping持有的屬性有:

 

MyBatis中使用到的設計模式

下麵來總結一下MyBatis中使用到的設計模式,有些設計模式可能在到目前位置的文章中沒有體現,但是在之後的【MyBatis源碼分析】系列文章中也會體現,這裡一併先列舉出來:

1、建造者模式

代碼示例為SqlSessionFactoryBuilder,代碼片段:

 1 public SqlSessionFactory build(Reader reader) {
 2     return build(reader, null, null);
 3   }
 4 
 5   public SqlSessionFactory build(Reader reader, String environment) {
 6     return build(reader, environment, null);
 7   }
 8 
 9   public SqlSessionFactory build(Reader reader, Properties properties) {
10     return build(reader, null, properties);
11   }
12 
13   public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
14     try {
15       XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
16       return build(parser.parse());
17     } catch (Exception e) {
18       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
19     } finally {
20       ErrorContext.instance().reset();
21       try {
22         reader.close();
23       } catch (IOException e) {
24         // Intentionally ignore. Prefer previous error.
25       }
26     }
27   }

重載了大量的build方法,可以根據參數的不同構建出不同的SqlSessionFactory。

2、抽象工廠模式

代碼示例為TransactionFactory,代碼片段為:

 1 public class JdbcTransactionFactory implements TransactionFactory {
 2 
 3   @Override
 4   public void setProperties(Properties props) {
 5   }
 6 
 7   @Override
 8   public Transaction newTransaction(Connection conn) {
 9     return new JdbcTransaction(conn);
10   }
11 
12   @Override
13   public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
14     return new JdbcTransaction(ds, level, autoCommit);
15   }
16 }

抽象出事物工廠,不同的事物類型實現不同的事物工廠,像這裡就是Jdbc事物工廠,通過Jdbc事物工廠去返回事物介面的具體實現。

其它的像DataSourceFactory也是抽象工廠模式的實現。

3、模板模式

代碼示例為BaseExecutor,代碼片段:

 1 protected abstract int doUpdate(MappedStatement ms, Object parameter)
 2       throws SQLException;
 3 
 4 protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
 5       throws SQLException;
 6 
 7 protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
 8       throws SQLException;
 9 
10 protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
11       throws SQLException;

BaseExecutor封裝好方法流程,子類例如SimpleExecutor去實現。

4、責任鏈模式

代碼示例為InterceptorChain,代碼片段為:

 1 public class InterceptorChain {
 2 
 3   private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
 4 
 5   public Object pluginAll(Object target) {
 6     for (Interceptor interceptor : interceptors) {
 7       target = interceptor.plugin(target);
 8     }
 9     return target;
10   }
11 
12   public void addInterceptor(Interceptor interceptor) {
13     interceptors.add(interceptor);
14   }
15   
16   public List<Interceptor> getInterceptors() {
17     return Collections.unmodifiableList(interceptors);
18   }
19 
20 }

可以根據需要添加自己的Interceptor,最終按照定義的Interceptor的順序逐一嵌套執行。

5、裝飾器模式

代碼示例為CachingExecutor,代碼片段為:

 1 public class CachingExecutor implements Executor {
 2 
 3   private Executor delegate;
 4   private TransactionalCacheManager tcm = new TransactionalCacheManager();
 5 
 6   public CachingExecutor(Executor delegate) {
 7     this.delegate = delegate;
 8     delegate.setExecutorWrapper(this);
 9   }
10 
11   ...
12 }

給Executor添加上了緩存的功能,update與query的時候會根據用戶配置先嘗試操作緩存。

在MyBatis中還有很多地方使用到了裝飾器模式,例如StatementHandler、Cache。

6、代理模式

代碼示例為PooledConnection,代碼片段為:

 1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 2     String methodName = method.getName();
 3     if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
 4       dataSource.pushConnection(this);
 5       return null;
 6     } else {
 7       try {
 8         if (!Object.class.equals(method.getDeclaringClass())) {
 9           // issue #579 toString() should never fail
10           // throw an SQLException instead of a Runtime
11           checkConnection();
12         }
13         return method.invoke(realConnection, args);
14       } catch (Throwable t) {
15         throw ExceptionUtil.unwrapThrowable(t);
16       }
17     }
18 }

這層代理的作用主要是為了讓Connection使用完畢之後從棧中彈出來。

MyBatis中的插件也是使用代理模式實現的,這個在後面會說到。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 轉載請註明出處:http://www.cnblogs.com/Joanna-Yan/p/6973266.html 前面講到:Spring+SpringMVC+MyBatis深入學習及搭建(九)——MyBatis和Spring整合 使用官方網站的mapper自動生成工具mybatis-generato ...
  • python中函數參數有:預設參數、關鍵字參數、非關鍵字可變長參數(元組)、關鍵字可變長參數(字典) ...
  • python numpy csv文件的寫入和存取 寫入csv文件 CSV (Comma‐Separated Value, 逗號分隔值),是一種常見的文件格式,用來存儲批量數據。 寫入csv文件 示例: 得到的文件是這樣的 改變參數,以浮點數寫入 讀取csv文件 讀取csv文件 示例: CSV只能有效 ...
  • 大學四年,即將畢業! 大學期間的最後一篇博客,總結分享下我做的畢業設計。我選的論文命題為《燃氣管網設備儀器進銷存管理系統之後臺設計》,由於我們專業只有我一個走技術路線,所以,我一個人完成了整個系統的設計及開發,總耗時近一個月,最終獲得優的成績。 這裡不討論論文,不寫具體實現細節,主要講如何一步步搭建 ...
  • Base64.java DESUtil.java ...
  • package com.mstf.aes; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmExcep... ...
  • RSAsecurity.java RSAtest.java ...
  • 背景: 線上機器,需要過濾access日誌,發送給另外一個api 期初是單進程,效率太低,改為多進程發送後,查看日誌中偶爾會出現異常錯誤(忘記截圖了。。。) 總之就是埠不夠用了報錯 原因: 每一條日誌都是一次請求發送給api,短連接產生大量time_wait狀態,占用了大量埠 這種高併發導致的大 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...