目錄
- 前言
- 環境版本
- 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中的重要元件做了初步的瞭解,為後面更深入的原始碼閱讀做了鋪墊,如果覺得作者寫的不錯,在看分享一波,謝謝支援。