Mybati源碼解析篇之六劍客!!!
- 2020 年 9 月 10 日
- 筆記
- JAVA, mybatis
目錄
- 前言
- 環境版本
- Mybatis的六劍客
- SqlSession
- 有何方法
- 語句執行方法
- 立即批量更新方法
- 事務控制方法
- 本地緩存方法
- 獲取映射方法
- 有何實現類?
- Executor
- 實現類
- BaseExecutor
- CachingExecutor
- SimpleExecutor
- BatchExecutor
- ReuseExecutor
- SpringBoot中如何創建?
- StatementHandler
- 實現類
- SimpleStatementHandler
- PreparedStatementHandler
- CallableStatementHandler
- RoutingStatementHandler
- ParameterHandler
- TypeHandler
- ResultSetHandler
- 總結
前言
環境版本
- 本篇文章講的一切內容都是基於
Mybatis3.5
和SpringBoot-2.3.3.RELEASE
。
Myabtis的六劍客
- 其實Mybatis的底層源碼和Spring比起來還是非常容易讀懂的,作者將其中六個重要的接口抽離出來稱之為
Mybatis的六劍客
,分別是SqlSession
、Executor
、StatementHandler
、ParameterHandler
、ResultSetHandler
、TypeHandler
。
- 六劍客在Mybatis中分別承擔著什麼角色?下面將會逐一介紹。
- 介紹六劍客之前,先來一張六劍客執行的流程圖,如下:

SqlSession
- SqlSession是Myabtis中的核心API,主要用來執行命令,獲取映射,管理事務。它包含了所有執行語句、提交或回滾事務以及獲取映射器實例的方法。
有何方法
- 其中定義了將近20個方法,其中涉及的到語句執行,事務提交回滾等方法。下面對於這些方法進行分類總結。
語句執行方法
- 這些方法被用來執行定義在 SQL 映射 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 語句。你可以通過名字快速了解它們的作用,每一方法都接受語句的 ID 以及參數對象,參數可以是原始類型(支持自動裝箱或包裝類)、JavaBean、POJO 或 Map。
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<T> Cursor<T> selectCursor(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
- 其中的最容易誤解的就是
selectOne
和selectList
,從方法名稱就很容易知道區別,一個是查詢單個,一個是查詢多個。如果你對自己的SQL無法確定返回一個還是多個結果的時候,建議使用selectList
。
insert
,update
,delete
方法返值是受影響的行數。
- select還有幾個重用的方法,用於限制返回行數,在Mysql中對應的就是
limit
,如下:
<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)
- 其中的
RowBounds
參數中保存了限制的行數,起始行數。
立即批量更新方法
- 當你將 ExecutorType 設置為 ExecutorType.BATCH 時,可以使用這個方法清除(執行)緩存在 JDBC 驅動類中的批量更新語句。
List<BatchResult> flushStatements()
事務控制方法
- 有四個方法用來控制事務作用域。當然,如果你已經設置了自動提交或你使用了外部事務管理器,這些方法就沒什麼作用了。然而,如果你正在使用由 Connection 實例控制的 JDBC 事務管理器,那麼這四個方法就會派上用場:
void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)
- 默認情況下 MyBatis 不會自動提交事務,除非它偵測到調用了插入、更新或刪除方法改變了數據庫。如果你沒有使用這些方法提交修改,那麼你可以在
commit
和 rollback
方法參數中傳入 true 值,來保證事務被正常提交(注意,在自動提交模式或者使用了外部事務管理器的情況下,設置 force
值對 session
無效)。大部分情況下你無需調用 rollback()
,因為 MyBatis 會在你沒有調用 commit
時替你完成回滾操作。不過,當你要在一個可能多次提交或回滾的 session 中詳細控制事務,回滾操作就派上用場了。
本地緩存方法
- Mybatis 使用到了兩種緩存:本地緩存(local cache)和二級緩存(second level cache)。
- 默認情況下,本地緩存數據的生命周期等同於整個 session 的周期。由於緩存會被用來解決循環引用問題和加快重複嵌套查詢的速度,所以無法將其完全禁用。但是你可以通過設置
localCacheScope=STATEMENT
來只在語句執行時使用緩存。
- 可以調用以下方法清除本地緩存。
void clearCache()
獲取映射器
- 在SqlSession中你也可以獲取自己的映射器,直接使用下面的方法,如下:
<T> T getMapper(Class<T> type)
UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);
有何實現類
- 在Mybatis中有三個實現類,分別是
DefaultSqlSession
,SqlSessionManager
、SqlSessionTemplate
,其中重要的就是DefaultSqlSession
,這個後面講到Mybatis執行源碼的時候會一一分析。
- 在與SpringBoot整合時,Mybatis的啟動器配置類會默認注入一個
SqlSessionTemplate
,源碼如下:
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)
//根據執行器的類型創建不同的執行器,默認CachingExecutor
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
Executor
- Mybatis的執行器,是Mybatis的調度核心,負責SQL語句的生成和緩存的維護,SqlSession中的crud方法實際上都是調用執行器中的對應方法執行。
- 繼承結構如下圖:

實現類
BaseExecutor
- 這是一個抽象類,採用模板方法的模式,有意思的是這個老弟模仿Spring的方式,真正的執行的方法都是
doxxx()
。
- 其中有一個方法值得注意,查詢的時候走的
一級緩存
,因此這裡注意下,既然這是個模板類,那麼Mybatis執行select的時候默認都會走一級緩存。代碼如下:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//此處的localCache即是一級緩存,是一個Map的結構
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//執行真正的查詢
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
CachingExecutor
- 這個比較有名了,二級緩存的維護類,與SpringBoot整合默認創建的就是這個傢伙。下面來看一下如何走的二級緩存,源碼如下:
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//查看當前Sql是否使用了二級緩存
Cache cache = ms.getCache();
//使用緩存了,直接從緩存中取
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//從緩存中取數據
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
//沒取到數據,則執行SQL從數據庫查詢
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//查到了,放入緩存中
tcm.putObject(cache, key, list); // issue #578 and #116
}
//直接返回
return list;
}
}
//沒使用二級緩存,直接執行SQL從數據庫查詢
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
SimpleExecutor
- 這個類像個直男,最簡單的一個執行器,就是根據對應的SQL執行,不會做一些額外的操作。
BatchExecutor
- 通過批量操作來優化性能。通常需要注意的是
批量更新
操作,由於內部有緩存的實現,使用完成後記得調用flushStatements
來清除緩存。
ReuseExecutor
- 可重用的執行器,重用的對象是Statement,也就是說該執行器會緩存同一個sql的
Statement
,省去Statement的重新創建,優化性能。
- 內部的實現是通過一個
HashMap
來維護Statement對象的。由於當前Map只在該session中有效,所以使用完成後記得調用flushStatements
來清除Map。
SpringBoot中如何創建
- 在SpringBoot到底創建的是哪個執行器呢?其實只要閱讀一下源碼可以很清楚的知道,答案就在
org.apache.ibatis.session.Configuration
類中,其中創建執行器的源碼如下:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//沒有指定執行器的類型,創建默認的,即是SimpleExecutor
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//類型是BATCH,創建BatchExecutor
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
//類型為REUSE,創建ReuseExecutor
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
//除了上面兩種,創建的都是SimpleExecutor
executor = new SimpleExecutor(this, transaction);
}
//如果全局配置了二級緩存,則創建CachingExecutor,SpringBoot中這個參數默認是true,可以自己設置為false
if (cacheEnabled) {
//創建CachingExecutor
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
- 顯而易見,SpringBoot中默認創建的是
CachingExecutor
,因為默認的cacheEnabled
的值為true
。
StatementHandler
- 熟悉JDBC的朋友應該都能猜到這個接口是幹嘛的,很顯然,這個是對SQL語句進行處理和參數賦值的。
實現類
- 該接口也是有很多的實現類,如下圖:

SimpleStatementHandler
- 這個很簡單了,就是對應我們JDBC中常用的Statement接口,用於簡單SQL的處理
PreparedStatementHandler
- 這個對應JDBC中的PreparedStatement,預編譯SQL的接口。
CallableStatementHandler
- 這個對應JDBC中CallableStatement,用於執行存儲過程相關的接口。
RoutingStatementHandler
- 這個接口是以上三個接口的路由,沒有實際操作,只是負責上面三個StatementHandler的創建及調用。
ParameterHandler
ParameterHandler
在Mybatis中負責將sql中的佔位符替換為真正的參數,它是一個接口,有且只有一個實現類DefaultParameterHandler
。
setParameters
是處理參數最核心的方法。這裡不再詳細的講,後面會講到。
TypeHandler
- 這位大神應該都聽說過,也都自定義過吧,簡單的說就是在預編譯設置參數和取出結果的時候將Java類型和JDBC的類型進行相應的轉換。當然,Mybatis內置了很多默認的類型處理器,基本夠用,除非有特殊的定製,我們才會去自定義,比如需要將Java對象以
JSON
字符串的形式存入數據庫,此時就可以自定義一個類型處理器。
- 很簡單的東西,此處就不再詳細的講了,後面會單獨出一篇如何自定義類型處理器的文章。
ResultSetHandler
- 結果處理器,負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合或者
Cursor
。
- 具體實現類就是
DefaultResultSetHandler
,其實現的步驟就是將Statement執行後的結果集,按照Mapper文件中配置的ResultType或ResultMap來封裝成對應的對象,最後將封裝的對象返回。
- 源碼及其複雜,尤其是其中對嵌套查詢的解析,這裡只做個了解,後續會專門寫一篇文章介紹。
總結
- 至此,Mybatis源碼第一篇就已經講完了,本篇文章對Mybatis中的重要組件做了初步的了解,為後面更深入的源碼閱讀做了鋪墊,如果覺得作者寫的不錯,在看分享一波,謝謝支持。