數(shù)據(jù)庫驅(qū)動連接管理和SQL解析的管理組件之 Executor 執(zhí)行器
MyBatis 四大組件之 Executor 執(zhí)行器
? ? 每一個 SqlSession 都會擁有一個 Executor 對象,這個對象負(fù)責(zé)增刪改查的具體操作,我們可以簡單的將它理解為 JDBC中Statement 的封裝版。
Executor的繼承結(jié)構(gòu)

如圖所示,位于繼承體系最頂層的是 Executor 執(zhí)行器,它有兩個實現(xiàn)類,分別是BaseExecutor和 CachingExecutor。
BaseExecutor是一個抽象類,這種通過抽象的實現(xiàn)接口的方式是適配器設(shè)計模式之接口適配的體現(xiàn),是 Executor 的默認(rèn)實現(xiàn),實現(xiàn)了大部分 Executor 接口定義的功能,降低了接口實現(xiàn)的難度。
BaseExecutor 的子類有三個,分別是SimpleExecutor、ReuseExecutor和BatchExecutor。
SimpleExecutor:簡單執(zhí)行器。是MyBatis的默認(rèn)執(zhí)行器,每執(zhí)行一次update或select,就開啟一個Statement對象,用完就直接關(guān)閉Statement對象(可以是Statement或者PreparedStatment 對象)
ReuseExecutor:可重用執(zhí)行器,這里的重用是指重復(fù)使用Statement。它會在內(nèi)部使用一個Map把創(chuàng)建的Statement都緩存起來,每次執(zhí)行SQL命令時候都會去判斷是否存在基于該SQL的Statement對象,如果存在Statement對象并且對應(yīng)的Connection還沒有關(guān)閉情況下就繼續(xù)使用之前的Statement對象,并將其緩存起來。因為每次Sqlsession都有一個新的Executor對象,所以緩存在ReuseExecutor的Statement對象作用域是同一個Sqlsession.
BatchExecutor: 批處理執(zhí)行器,用于將多個SQL一次性輸出到數(shù)據(jù)庫
CachingExecutor: 緩存執(zhí)行器,先從緩存中查詢結(jié)果,如果存在,就返回;如果不存在,再委托給 Executor delegate 去數(shù)據(jù)庫中取,delegate 可以是上面任何一個執(zhí)行器。
Executor的設(shè)計用現(xiàn)實做例子
Executor執(zhí)行器是MyBatis的重要組件。
Executor相當(dāng)于外包的Boss,它定義了甲方(SQL)需要干的活(Executor的主要方法),boss接到開發(fā)任務(wù)時候會先找到項目經(jīng)理(CachingExecutor),項目經(jīng)理幾乎不懂技術(shù),它主要和技術(shù)leader(BaseExecutor)打交道,技術(shù)leader主要負(fù)責(zé)框架搭建,具體工作都會交給下面的程序員來做。但程序員也有優(yōu)劣,高級程序員(BatchExecutor)、中級程序員(ReuseExecutor)、初級程序員(SimpleExecutor),
它們干的活也不一樣。一般有新的項目需求傳達(dá)到項目經(jīng)理這里,項目經(jīng)理先判斷自己手里有沒有現(xiàn)成的類庫或者項目直接套用(Cache),有的話就直接讓技術(shù)leader拿來直接套用就好,沒有的話就需要搭建框架,再把框架存入本地類庫中,再進(jìn)行解析。
Executor創(chuàng)建過程以及源碼分析
直接使用MyBatis方式來分析。
創(chuàng)建完 SqlSessionFactory 之后,調(diào)用其openSession方法:
SqlSession sqlSession=factory.openSession();
SqlSessionFactory 的默認(rèn)實現(xiàn)是 DefaultSqlSessionFactory,所以我們需要關(guān)心的就是 DefaultSqlSessionFactory 中的 openSession() 方法
openSession 調(diào)用的是openSessionFromDataSource方法,傳遞執(zhí)行器的類型,方法傳播級別,是否自動提交,然后在 openSessionFromDataSource 方法中會創(chuàng)建一個執(zhí)行器

調(diào)用 newExecutor 方法,根據(jù)傳入的 ExecutorType 類型來判斷是哪種執(zhí)行器,然后執(zhí)行相應(yīng)的邏輯

ExecutorType 的選擇:
ExecutorType 來決定 Configuration 對象創(chuàng)建何種類型的執(zhí)行器,它的賦值可以通過兩個地方進(jìn)行賦值:
可以通過 settings 標(biāo)簽來設(shè)置當(dāng)前工程中所有的 SqlSession 對象使用默認(rèn)的 Executor
<settings><!--取值范圍 SIMPLE,REUSE,BATCH--><setting name="defaultExecutorType"value="SIMPLE"/></settings>
另外一種直接通過Java對方法賦值的方式
session=factory.openSession(ExecutorType.BATCH);
ExecutorType 是一個枚舉,它只有三個值 SIMPLE, REUSE, BATCH
創(chuàng)建完成 Executor 之后,會把 Executor 執(zhí)行器放入一個 DefaultSqlSession 對象中來對四個屬性進(jìn)行賦值,他們分別是configuration、executor、dirty、autoCommit
Executor接口的主要方法
增、更、刪 :
int update(MappedStatement var1, Object var2)throws SQLException;
查詢,帶分頁,帶緩存,BoundSql :
<E>? List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6)throws SQLException;
??查詢,帶分頁:
<E>?List?<E>?query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4)throws SQLException;
通過游標(biāo)查詢(數(shù)據(jù)量大時候用):
<E>?Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3)throws SQLException;
刷新批處理語句:
List<BatchResult> flushStatements()throws SQLException;
提交:
void commit(boolean var1)throws SQLException;
回滾:
void rollback(boolean var1)throws SQLException;
創(chuàng)建緩存KEY:
CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);
判斷是否緩存了:
boolean isCached(MappedStatement var1, CacheKey var2);
清除session緩存:
void clearLocalCache();
延遲加載:
void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class var5);
Transaction getTransaction();
void close(boolean var1);
boolean isClosed();
void setExecutorWrapper(Executor var1);
Executor 接口的方法還是比較多的,這里我們就幾個主要的方法和調(diào)用流程來做一個簡單的描述

緩存機制角度:

從查詢query方法角度解剖:
query 方法有兩種形式,一種是直接查詢;一種是從緩存中查詢,下面來看一下源碼以及時序圖:
當(dāng)有一個查詢請求訪問的時候,首先會經(jīng)過 Executor 的實現(xiàn)類 CachingExecutor,先從緩存中查詢 SQL 是否是第一次執(zhí)行,如果是第一次執(zhí)行的話,那么就直接執(zhí)行 SQL 語句,并創(chuàng)建緩存,如果第二次訪問相同的 SQL 語句的話,那么就會直接從緩存中提取
CachingExecutor:
// 第一次查詢,并創(chuàng)建緩存

MapperStatement維護了一條 select、update、delete、insert 節(jié)點的封裝,包括資源(resource),配置(configuration),SqlSource(sql源文件)等。使用Configuration 的 getMappedStatement 方法來獲取 MappedStatement 對象。
BoundSql這個類包括 SQL 的基本信息,基本的 SQL 語句,參數(shù)映射,參數(shù)類型等

調(diào)用到 CachingExecutor 類中的 query 查詢緩存的方法

Executor 執(zhí)行器所起的作用相當(dāng)于是管理 StatementHandler 的整個生命周期的工作,包括創(chuàng)建、初始化、解析、關(guān)閉。
ReuseExecutor 完成的 doQuery 工作:幾乎和 SimpleExecutor 完成的工作一樣,其內(nèi)部不過是使用一個 Map 來存儲每次執(zhí)行的查詢語句,為后面的 SQL 重用作準(zhǔn)備。
BatchExecutor 完成的 doQuery 工作和 SimpleExecutor 完成的工作一樣。
Executor的執(zhí)行查詢的大致流程時序圖:

update() 方法(update 鏈?zhǔn)莍nsert、update、delete 在語義上其實都是更新的意思)
ReuseExecutor 完成的 doUpdate 工作:幾乎和 SimpleExecutor完成的工作一樣,其內(nèi)部不過是使用一個Map來存儲每次執(zhí)行的更新語句,為后面的SQL重用作準(zhǔn)備。
BatchExecutor 完成的 doUpdate 工作:和 SimpleExecutor 完成的工作相似,只是其內(nèi)部有一個 List 列表來一次行的存儲多個 Statement,用于將多個 sql 語句一次性輸送到數(shù)據(jù)庫執(zhí)行。
update時序圖:

queryCursor()方法
我們查閱其源碼的時候,在執(zhí)行器的執(zhí)行過程中并沒有發(fā)現(xiàn)其與 query 方法有任何不同之處,但是在doQueryCursor方法中我們可以看到它返回了一個 cursor 對象,網(wǎng)上搜索cursor 的相關(guān)資料并查閱其基本結(jié)構(gòu),得出來的結(jié)論是:用于逐條讀取 SQL 語句,應(yīng)對數(shù)據(jù)量
// 查詢可以返回Cursor<T>類型的數(shù)據(jù),類似于JDBC里的ResultSet類,
// 當(dāng)查詢百萬級的數(shù)據(jù)的時候,使用游標(biāo)可以節(jié)省內(nèi)存的消耗,不需要一次性取出所有數(shù)據(jù),可以進(jìn)行逐條處理或逐條取出部分批量處理。
public? interfaceCursor<T>extendsCloseable,Iterable<T>? ?{
booleanisOpen();
booleanisConsumed();
intgetCurrentIndex();
}
flushStatements() 方法
主要用來釋放 statement,或者用于 ReuseExecutor 和 BatchExecutor 來刷新緩存。
createCacheKey() 方法
createCacheKey() 方法主要由 BaseExecutor 來執(zhí)行并創(chuàng)建緩存,MyBatis 中的緩存分為一級緩存和二級緩存
Executor 中的其他方法
Executor 中還有其他方法,提交 commit,回滾 rollback,判斷是否時候緩存 isCached,關(guān)閉 close,獲取事務(wù) getTransaction 立即清除本地緩存 clearLocalCache 等