封面:洛小汐
作者:潘潘


2020 年的大疫情,把世界撕成幾片,
時至今日,依舊人心惶惶,
很慶幸,身處這安穩國,
兼得一份安穩工,
·
東家常講的一個詞:深秋心態 ,
大勢時,不跟風、起哄,
蕭條時,不放棄播種和耕耘的信心,
熱時不燥、冷時不棄,
這就是深秋心態,
·
大疫情,相信只是大自然的規律,
也恰是我們保持深秋心態的時候,
默默播種和耕耘吧,
今年,世界會慢慢復蘇,希望都會來臨,

2021年要信心滿滿 ヾ(?°?°?)ノ゙
定會識訓滿滿 ~
上圖保存可做朋友圈封面圖 ~
前言
上節我們介紹了 《 Mybatis系列全解(五):全網最全!詳解Mybatis的Mapper映射檔案 》,經此一文,我們基本能掌握 Mapper 映射器九大頂級元素的基本用法和其中技巧,在本節,我們開始深入,我挑選了 Mybatis 框架中幾個比較硬核的 API ,跟大家一起探討,夯實了這些 API ,有助于你學習理解整個 Mybatis 框架,特別是 Mybatis 核心的資料處理層,你絕對會形成一套清晰的脈絡印記,總之,希望大家都能成為 Mybatis King !
另外, 我們的 Mybatis 全解系列一直在更新

Mybaits系列全解 (持續更新)
- Mybatis系列全解(一):手寫一套持久層框架
- Mybatis系列全解(二):Mybatis簡介與環境搭建
- Mybatis系列全解(三):Mybatis簡單CRUD使用介紹
- Mybatis系列全解(四):全網最全!Mybatis組態檔XML全貌詳解
- Mybatis系列全解(五):全網最全!詳解Mybatis的Mapper映射檔案
- Mybatis系列全解(六):Mybatis最硬核的API你知道幾個?
- Mybatis系列全解(七):Dao層兩種實作方式
- Mybatis系列全解(八):Mybatis的動態SQL
- Mybatis系列全解(九):Mybatis的復雜映射
- Mybatis系列全解(十):Mybatis注解開發
- Mybatis系列全解(十一):Mybatis快取全解
- Mybatis系列全解(十二):Mybatis插件開發
- Mybatis系列全解(十三):Mybatis代碼生成器
- Mybatis系列全解(十四):Spring集成Mybatis
- Mybatis系列全解(十五):SpringBoot集成Mybatis
- Mybatis系列全解(十六):Mybatis原始碼剖析
本文目錄
1、Mybatis 架構與核心API
2、Configuration – 全域配置物件
3、Resources – 資源輔助類
4、SqlSessionFactoryBuilder – 會話工廠構建器
5、SqlSessionFactory – 會話工廠
6、SqlSession – 會話
7、Executor – 執行器
8、StatementHandler – 陳述句處理器
9、ParamerHandler – 引數處理器
10、ResultSetHandler – 結果集處理器
11、TypeHandler – 型別轉換器
12、MappedStatement – 陳述句物件
13、SqlSource – SQL源
14、BoundSql – SQL陳述句

1、Mybatis 架構與核心API
不出意外的話,在后續原始碼剖析相關文章中,我們會對 Mybatis 的原始碼進行一次大掃蕩,一起挖掘每一處值得大家深入理解/記憶的知識點,而在本文中,我們主要先把 Mybatis 的架構/層次鋪開,俯視 Mybatis 架構的設計全貌,再把幾個硬核的 API 詳細消化,
整體順序脈絡,希望讓你有所期待 ~
我們先簡單揭開 Mybatis 神秘的原始碼包,
瞅瞅 Ta 大致目錄結構 :

看,Mybatis 的源代碼包整齊劃一排在 org.apache.ibatis 目錄下,基本設計用途我簡單梳理成上面這張圖,方便大家直觀理解,當然只看原始碼包目錄結構,難免會顯得枯燥無物,所以我們再看一下,其實 Mybatis 的原始碼包功能上可以是這么劃分:

上圖讓我們對 Mybatis 的架構有了抽象的理解,
然而,實際上具體的職能分工,核心 API 的場景應用,到底會是怎樣一套流程呈現呢?
看下面這幅功能架構設計,或許你能更好的理解,

根據 Mybatis 功能架構我們劃分成三層:
-
介面層:該層提供一系列介面讓用戶直接參與使用,包含資訊配置與實際資料操作呼叫,配置方式包括:基于 XML 配置方式、基于 Java API 配置方式兩種方式,用戶也可以通過介面層 API 對資料庫發起增刪改查等操作的請求, 本層介面會把接收到的呼叫請求交給資料處理層的構件去處理,
-
資料處理層:該層是 Mybatis 的核心層,負責資料處理,主要包括SQL 引數映射決議、SQL 陳述句的實際執行、執行結果集的映射處理等,
-
框架支撐層:該層屬于 Mybatis 的后勤保障層,包括資料庫連接管理、事務把控管理、配置加載與快取處理、日志處理、例外處理等,提供基礎支撐能力,保障上層的資料處理,
我們知道,Mybatis 框架讓用戶只需要提供配置資訊,并且專注于 SQL 的撰寫即可,對于連接管理資料庫/事務,或實際的 SQL 引數映射/陳述句執行/結果集映射等操作,作為用戶都并不需要操心和參與,
但是,好奇的我們其實想知道,Mybatis 核心部分的資料處理在整體流程中,是如何支撐用戶請求?同時各個構件之間互動,又是怎樣流轉呢?
很巧,我這里有一張圖,介紹了整體流程:

根據以上框架流程圖進行講解:
- 創建配置并呼叫API:這一環節發生在應用程式端,是開發人員在實際應用程式中進行的兩步操作,第一步創建核心組態檔 Configuration.xml 和映射檔案 mapper.xml (通過注解方式也可創建以上兩種配置),準備好基礎配置和 SQL 陳述句之后;第二步就是直接呼叫 Mybatis 框架中的資料庫操作介面,
- 加載配置并初始化:Mybatis 框架會根據應用程式端提供的核心組態檔與 SQL 映射檔案的內容,使用資源輔助類 Resources 把組態檔讀取成輸入流,然后通過對應的決議器決議并封裝到 Configuration 物件和 MappedStatement 物件,最終把物件存盤在記憶體之中,
- 創建會話并接收請求:在 Mybatis 框架加載配置并初始化配置物件之后,會話工廠構建器 SqlSessionFactoryBuilder 同時創建會話工廠 SqlSessionFactory,會話工廠會根據應用程式端的請求,創建會話 SqlSession,以便應用程式端進行資料庫互動,
- 處理請求:SqlSession 接收到請求之后,實際上并沒有進行處理,而是把請求轉發給執行器 Executor,執行器再分派到陳述句處理器 StatementHandler ,陳述句處理器會結合引數處理器 ParameterHandler ,進行資料庫操作(底層封裝了 JDBC Statement 操作),
- 回傳處理結果:每個陳述句處理器 StatementHandler 處理完成資料庫操作之后,會協同 ResultSetHandler 以及型別處理器 TypeHandler ,對底層 JDBC 回傳的結果集進行映射封裝,最侄訓傳封裝物件,
針對以上總體框架流程涉及到的這些硬核 API,下面我們逐個展開介紹,但不會詳細剖析原始碼與原理,包括構建細節,因為這些我們在后續的原始碼剖析章節中都會詳細分析,

2、Configuration – 全域配置物件
對于 Mybatis 的全域配置物件 Configuration,我相信無論是初學者還是資深玩家,都不會陌生,整個 Mybatis 的宇宙,都圍繞著 Configuration 轉,Configuration 物件的結構和 config.xml 組態檔的內容幾乎相同,涵蓋了properties (屬性),settings (設定),typeAliases (型別別名),typeHandlers (型別處理器),objectFactory (物件工廠),mappers (映射器)等等,之前我們有專門的一篇文章詳細進行介紹,感興趣的朋友往上翻到目錄串列,找到 《Mybatis系列全解(四):全網最全!Mybatis組態檔XML全貌詳解》 一文詳細品味一番吧,

配置物件 Configuration 通過決議器 XMLConfigBuilder 進行決議,把全域組態檔 Config.xml 與 映射器組態檔 Mapper.xml 中的配置資訊全部構建成完整的 Configuration 物件,后續我們原始碼分析時詳細剖析整個程序,

3、Resources – 資源輔助類
我們知道,像 Configuration 和 Mapper 的配置資訊存放在 XML 檔案中,Mybatis 框架在構建配置物件時,必須先把 XML 檔案資訊加載成流,再做后續的決議封裝,而 Resources 作為資源的輔助類,恰恰干的就是這個活,無論是通過加載本地資源或是加載遠程資源,最終都會通過 類加載器 訪問資源檔案并輸出檔案流,

//加載核心組態檔
InputStream resourceAsStream =
Resources.getResourceAsStream("Config.xml");
Resources 實實在在提供了一系列方法分分鐘解決你的檔案讀取加載問題:


4、SqlSessionFactoryBuilder – 會話工廠構建器
我們一撞見 xxxBuilder ,就大致能知道它是某類物件的構建器,這里 SqlSessionFactoryBuilder 也是一樣,它是 Mybatis 中的一個會話工廠構建器,在資源輔助類 Resources 讀取到檔案流資訊之后,它負責決議檔案流資訊并構建會話工廠 SqlSessionFactory,(決議的配置檔案包含:全域配置 Configuration 與映射器 Mapper)

在程式應用端,我們一般使用 SqlSessionFactoryBuilder 直接構建會話工廠:
// 獲得sqlSession工廠物件
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(resourceAsStream);
當然,如果你集成了 Spring 框架的專案,則不需要自己手工去構建會話工廠,直接在 Spring 組態檔中指定即可,例如指定一個 bean 物件,id 是 sqlSessionFactory,而 class 類指定為 org.mybatis.spring.SqlSessionFactoryBean ,
SqlSessionFactoryBuilder 內部通過決議器 XMLConfigBuilder 決議了檔案流,同時封裝成為配置物件 Configuration ,再把 Configuration 物件進行傳遞并構建實體,
public SqlSessionFactory build(
InputStream inputStream,
String environment,
Properties properties) {
// 配置決議器決議
XMLConfigBuilder parser =
new XMLConfigBuilder(
inputStream,environment, properties);
// 最終實體會話工廠
return build(parser.parse());
}
最終實體會話工廠,其實 Mybatis 默認實作是 new 了一個DefaultSqlSessionFactory 實體,
// 最終實體會話工廠
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
會話工廠構建器 SqlSessionFactoryBuilder 應用了構建者模式,主要目的就是為了構建 SqlSessionFactory 物件,以便后續生產 SqlSession 物件,這個構造器基本上算是 Mybatis 框架的入口構建器,它提供了一系列多型方法 build(),支持用戶使用 XML 組態檔或 Java API (Properties)來構建會話工廠 SqlSessionFactory 實體,
SqlSessionFactoryBuilder 的一生只為成就 SqlSessionFactory,當 SqlSessionFactory 一經實體,SqlSessionFactoryBuilder 使命完成,便可消亡,便可被丟棄,

因此 SqlSessionFactoryBuilder 實體的最佳作用域是 方法作用域(也就是區域方法變數), 你可以重用 SqlSessionFactoryBuilder 來創建多個 SqlSessionFactory 實體,但最好不要一直保留著它,以保證所有的 XML 決議資源可以被釋放給更重要的事情,
SqlSessionFactoryBuilder 中靈活構建會話工廠的一系列介面:


5、SqlSessionFactory – 會話工廠
會話工廠 SqlSessionFactory 是一個介面,作用是生產資料庫會話物件 SqlSession ,有兩個實作類:
- **DefaultSqlSessionFactory **(默認實作)
- **SqlSessionManager **(僅多實作了一個 Sqlsession 介面,已棄用)
在介紹會話工廠構建器 SqlSessionFactoryBuilder 的時候,我們了解到構建器默認創建了 DefaultSqlSessionFactory 實體,并且會話工廠本身會系結一個重要的屬性 Configuration 物件,在生產會話時,最終也會把 Configuration 配置物件傳遞并設定到會話 SqlSession 上,

會話工廠可以簡單創建 SqlSession 實體:
// 創建 SqlSession 實體
SqlSession session = sqlSessionFactory.openSession();
會話工廠創建 SqlSession 時,會系結資料源、事務處理、執行器等等,默認會話工廠實作類 DefaultSqlSessionFactory 在創建會話物件時,最終都會呼叫 openSessionFromDataSource 方法 ,即是如此實作:
// 每一個 openSession 最終都會呼叫此處
private SqlSession openSessionFromDataSource(
ExecutorType execType,
TransactionIsolationLevel level,
boolean autoCommit) {
// 環境配置
final Environment environment =
configuration.getEnvironment();
// 事務工廠
final TransactionFactory transactionFactory =
getTransactionFactoryFromEnvironment(environment);
// 事務
Transaction tx =
transactionFactory.newTransaction(
environment.getDataSource(),
level,
autoCommit);
// 執行器
final Executor executor =
configuration.newExecutor(tx, execType);
// 最終生成會話
return new DefaultSqlSession(
configuration, executor, autoCommit);
}
另外,會話工廠其實提供了一系列介面來靈活生產會話 SqlSession,你可以指定:
- 事務處理:你希望在 session 作用域中使用/開啟事務作用域(也就是不自動提交事務),還是使用自動提交(auto-commit),sqlSession 默認不提交事務,對于增刪改操作時需要手動提交事務,
- 資料庫連接:你希望 MyBatis 幫你從已配置的資料源獲取連接,還是使用自己提供的連接,可以動態創建資料源物件 Connection,
- 執行器型別:你希望指定某類執行器來創建/執行/預處理陳述句,可以有普通執行器(SimpleExecutor),或復用執行器(ReuserExecutor)、還是批量執行器(BatchExecutor)等,下面介紹執行器時會詳細說明,
- 事務隔離級別支持:支持 JDBC 的五個隔離級別(NONE、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE),對于事務相關的內容,我們后續 《spring 系列全解》 會詳細講,這里簡單說明一下就是事務隔離級別主要為了解決例如臟讀、不可重復讀、幻讀等問題,使用不同的事務隔離級別勢必會導致不同的資料庫執行效率,因此我們再不同的系統/功能中,對隔離級別有不同的需求,

SqlSessionFactory 一旦被創建就應該在 應用的運行期間 一直存在,沒有任何理由丟棄它或重新創建另一個實體, 使用 SqlSessionFactory 的最佳實踐是在應用運行期間不要重復創建多次,多次重建 SqlSessionFactory 被視為一種代碼“壞習慣”,因此 SqlSessionFactory 的最佳作用域是 應用作用域, 最簡單的就是使用單例模式或者靜態單例模式,
請記住,創建 SqlSessionFactory ,一次就好!
每個資料庫對應一個 SqlSessionFactory 實體,SqlSessionFactory 一旦被創建, 它的生命周期應該與應用的生命周期相同 ,所以,如果你想連接兩個資料庫,就需要創建兩個 SqlSessionFactory 實體,每個資料庫對應一個;而如果是三個資料庫,就需要三個實體,依此類推,


6、SqlSession – 會話
SqlSession 是一個介面,有兩個實作類:
- DefaultSqlSession(默認實作)
- SqlSessionManager (已棄用)
簡單來說,通過會話工廠構建出 SqlSession 實體之后,我們就可以進行增刪改查了,默認實體 DefaultSqlSession 提供了如此多的方法供用戶使用,有超過30個:

sqlSession 的方法除了 CURD,還提供了事務的控制例如提交/關閉/回滾等、提供了配置物件的獲取例如 getConfiguration()、提供了批量陳述句的執行更新例如 flushStatements()、提供了快取清除例如 clearCache() 、提供了映射器的使用 getMapper() 等等,

對于客戶端應用層面來說,熟悉 sqlSession 的 API 基本就可以任意操作資料庫了,不過我們希望想進一步了解 sqlSession 內部是如何執行 sql 呢?其實 sqlSession 是 Mybatis 中用于和資料庫互動的 頂層類,通常將它與本地執行緒 ThreadLocal 系結,一個會話使用一個 SqlSession,并且在使用完畢之后進行 關閉,
之所以稱 SqlSession 為資料互動的 頂層類,是它其實沒有完成實質的資料庫操作,根據之前的架構設計流程我們已經清晰的知道,SqlSession 對資料庫的操作都會轉發給具體的執行器 Executor 來完成 ;當然執行器也是甩手掌柜,執行器 Executor 會再分派給陳述句處理器 StatementHandler ,陳述句處理器會結合引數處理器 ParameterHandler ,共同完成最終的資料庫執行處理操作(底層還是封裝了 JDBC Statement 操作),并在每個陳述句處理器 StatementHandler 處理完成資料庫操作之后, 通過結果結處理器 ResultSetHandler 以及型別處理器 TypeHandler ,對底層 JDBC 回傳的結果集進行映射封裝,最終才回傳預期的封裝物件,
關注以下圖示 sqlSession 紅色高亮位置,詳細描述了會話的實際執行路徑:

SqlSession 可以理解為一次資料庫會話,一次會話當中既可以執行一次 sql ,也允許你批量執行多次,但是一旦會話關閉之后想要再執行 sql,那就必須重新創建會話,

每個執行緒都應該有它自己的 SqlSession 實體,SqlSession 的實體不是執行緒安全的,因此是不能被共享的,所以它的最佳的作用域是 請求(request)或方法(method) 作用域, 絕對不能將 SqlSession 實體的參考放在一個類的靜態域,甚至一個類的實體變數也不行, 也絕不能將 SqlSession 實體的參考放在任何型別的托管作用域中,比如 Servlet 框架中的 HttpSession, 如果你現在正在使用一種 Web 框架,考慮將 SqlSession 放在一個和 HTTP 請求相似的作用域中, 換句話說,每次收到 HTTP 請求,就可以打開一個 SqlSession,回傳一個回應后,就關閉它, 這個關閉操作很重要,為了確保每次都能執行關閉操作,你應該把這個關閉操作放到 finally 塊中,
Spring 集成 Mybatis 之后,通過依賴注入可以創建執行緒安全的、基于事務的 SqlSession ,并管理他們的生命周期,推薦搭配使用,

7、Executor – 執行器
Executor 是一個執行器介面,是 Mybatis 的調度核心,它定義了一組管理 Statement 物件與獲取事務的方法,并負責 SQL 陳述句的生成和一級/二級查詢快取的維護等,SqlSessionFactory 在創建 SqlSession 時會同時創建執行器,并指定執行器型別,默認使用 SimpleExecutor ,執行器介面有5個子孫實作類,其中 BaseExecutor 是抽象類,另外4個子孫實作類分別是:SimpleExecutor 、BatchExecutor、ReuseExecutor、CachingExecutor,

-
BaseExecutor:基礎執行器(抽象類),對Executor介面進行了基本實作,為下一級實作類執行器提供基礎支持,BaseExecutor 有三個子類分別是 SimpleExecutor、ResuseExecutor、BatchExecutor,
-
SimpleExecutor:普通執行器,繼承 BaseExecutor 抽象類,是 MyBatis 中 默認 使用的執行器. 每執行一次 update 或 select ,就開啟一個 Statement 物件,用完立刻關閉 Statement 物件,(可以是 Statement 或 PrepareStatement 物件),
-
ReuseExecutor:復用執行器,繼承 BaseExecutor 抽象類,這里的復用指的是重復使用 Statement . 它會在內部利用一個 Map 把創建的 Statement 都快取起來,每次在執行一條 SQL語 句時,它都會去判斷之前是否存在基于該 SQL 快取的 Statement 物件,存在且之前快取的 Statement 物件對應的 Connection 還沒有關閉則會繼續使用之前的 Statement 物件,否則將創建一個新的 Statement 物件,并將其快取起來,因為每一個新的 SqlSession 都有一個新的 Executor 物件,所以我們快取在 ReuseExecutor 上的 Statement 的作用域是同一個 SqlSession ,
-
BatchExecutor:批處理執行器,繼承 BaseExecutor 抽象類,通過批量操作來提高性能,用于將多個 sql 陳述句一次性輸送到資料庫執行,由于內部有快取的實作,所以使用完成后需要呼叫 flushStatements() 來清除快取,
-
CachingExecutor : 快取執行器,繼承 BaseExecutor 抽象類,它為 Executor 物件增加了 二級快取 的相關功能,cachingExecutor 有一個重要屬性 delegate,即為委托的執行器物件,可以是 SimpleExecutor、ReuseExecutor、BatchExecutor 中任意一個,CachingExecutor 在執行資料庫 update 操作時,它直接呼叫 委托物件 delegate 的 update 方法;而執行查詢時,它會先從快取中獲取查詢結果,存在就回傳,不存在則委托 delegate 去資料庫取,然后存盤到快取 cache 中,
Mybatis 在構建 Configuration 配置類時默認把 ExecutorType.SIMPLE 作為執行器型別,當我們的會話工廠 DefaultSqlSessionFactory 開始生產 SqlSession 會話時,會同時構建執行器,此時就會依據配置類 Configuration 構建時指定的執行器型別來實體具體執行器 ,流程如下:
// 1、Configuration配置類構建時
// 指定了默認執行器型別為:普通執行器
protected ExecutorType defaultExecutorType
= ExecutorType.SIMPLE;
// 2、Configuration配置類中
// 提供方法獲取默認執行器型別
public ExecutorType getDefaultExecutorType() {
return defaultExecutorType;
}
// 3、會話工廠創建 SqlSession 實體時
SqlSession session = sqlSessionFactory.openSession();
// 4、openSession 實際邏輯
public SqlSession openSession() {
return
openSessionFromDataSource(
// 這里可就獲取了默認執行器
configuration.getDefaultExecutorType(),
null,
false
);
}
這里,肯定有人想知道,我們能否指定其它執行器呢?
答案是:當然可以,有兩種方式指定:
- 第一種方式是通過 Java API 指定,在開啟會話 openSession 時進行指定,例如:
// 創建 SqlSession 實體
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)
// ExecutorType是一個列舉
// 有三個值SIMPLE, REUSE, BATCH
- 另一種通過 Configuration 配置方式來指定默認執行器型別,例如
<settings>
<!--取值范圍 SIMPLE, REUSE, BATCH -->
<setting name="defaultExecutorType" value="REUSE"/>
</settings>
對于第二種 settings 的配置方式,其實之前我們在介紹 Mybatis 的組態檔中已經講過,這里再簡單說明一下,像上面配置 settings 中的屬性 defaultExecutorType ,基本這些屬性都是 Mybatis 額外提供給我們靈活設定的,就算我們不設定 Mybatis 也會有默認值,例如像 defaultExecutorType 的默認值就是 SIMPLE,你看一下 Mybatis 在決議 Configuration 配置時的默認構建,就會明白:
決議 Configuration 的決議器(類與具體方法的代碼路徑):
org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsElement
我們截取部分代碼邏輯,下面是設定默認執行器型別屬性 defaultExecutorType 的內容:
// 組態檔決議器
public class XMLConfigBuilder {
// 最終決議到的 Configuration 物件
protected final Configuration configuration;
// 為 Configuration 物件設定屬性
private void settingsElement(Properties props) {
// 設定默認執行器型別,默認是 SIMPLE
configuration.setDefaultExecutorType(
ExecutorType.valueOf(
props.getProperty(
"defaultExecutorType", "SIMPLE")));
// .... 當然這里還有很多屬性設定
// .... 只要你在<settings>中配置即可
}
}
注意,到此我們知道可以根據業務需要指定執行器型別,例如 SIMPLE(普通執行器), REUSE(復用執行器), BATCH(批處理執行器),
但是,有朋友就好奇了,那快取執行器 CachingExecutor 好像不見說明呢?
確實如此,因為快取執行器個其它三個執行器還不太一樣,CachingExecutor 是需要我們開啟二級快取才會有,這里大家先不要思考什么是一級快取,什么二級快取,后續我們有一文會詳細講快取整個知識內容,
大家先了解一個概念即可,就是 Mybatis 的一級快取是默認開啟的,不管你要不要,都會有一級快取,而二級快取呢,是默認關閉的,但允許我們手工開啟,
對比開啟二級快取前后,執行器執行的區別吧!
- 不開啟二級快取,執行器執行時:

- 開啟二級快取之后,執行器執行時:

其實我們實際操作資料庫,不會直接接觸到執行器 Executor ,不過我們確實可以了解一下基本的執行原理,下面列出了執行器介面提供的眾多多載方法,基本用于事務/快取/資料庫管理與訪問,可以知道一下:

到此,對于執行器有了基本的認識,但是實際上,我們知道執行器自身沒有去具體執行 SQL 陳述句,而是分派到陳述句處理器 StatementHandler ,陳述句處理器會結合引數處理器 ParameterHandler ,最終進行資料庫操作,

8、StatementHandler – 陳述句處理器
StatementHandler 是一個陳述句處理器介面,它封裝了 JDBC Statement 操作,負責對 JDBC Statement 的操作,如 設定引數、結果集映射,是實際跟資料庫做互動的一道,StatementHandler 陳述句處理器實體,是在執行器具體執行 CRUD 操作時構建的,默認使用 PrepareStatementHandler,陳述句處理器介面有5個子孫實作類,其中 BaseStatementHandler 是抽象類,另外4個子孫實作類分別是:SimpleStatementHandler、PrepareStatementHandler、CallableStatementHandler、RoutingStatementHandler,

-
BaseStatementHandler:基礎陳述句處理器(抽象類),它基本把陳述句處理器介面的核心部分都實作了,包括配置系結、執行器系結、映射器系結、引數處理器構建、結果集處理器構建、陳述句超時設定、陳述句關閉等,并另外定義了新的方法 instantiateStatement 供不同子類實作以便獲取不同型別的陳述句連接,子類可以普通執行 SQL 陳述句,也可以做預編譯執行,還可以執行存盤程序等,
-
SimpleStatementHandler:普通陳述句處理器,繼承 BaseStatementHandler 抽象類,對應 java.sql.Statement 物件的處理,處理普通的不帶動態引數運行的 SQL,即執行簡單拼接的字串陳述句,同時由于 Statement 的特性,SimpleStatementHandler 每次執行都需要編譯 SQL (注意:我們知道 SQL 的執行是需要編譯和決議的),
-
PreparedStatementHandler:預編譯陳述句處理器,繼承 BaseStatementHandler 抽象類,對應 java.sql.PrepareStatement 物件的處理,相比上面的普通陳述句處理器,它支持可變引數 SQL 執行,由于 PrepareStatement 的特性,它會進行預編譯,在快取中一旦發現有預編譯的命令,會直接決議執行,所以減少了再次編譯環節,能夠有效提高系統性能,并預防 SQL 注入攻擊(所以是系統默認也是我們推薦的陳述句處理器),
-
CallableStatementHandler:存盤程序處理器,繼承 BaseStatementHandler 抽象類,對應 java.sql.CallableStatement 物件的處理,很明了,它是用來呼叫存盤程序的,增加了存盤程序的函式呼叫以及輸出/輸入引數的處理支持,
其實普通陳述句處理器、預執行陳述句處理器以及存盤程序處理器,只是 Mybatis 對于 JDBC 的陳述句執行物件的簡單包裝而已,沒有特別神秘,看以下 JDBC 的陳述句執行物件的類圖關系也就能夠清楚,
- RoutingStatementHandler:路由陳述句處理器,直接實作了 StatementHandler 介面,作用如其名稱,確確實實只是起到了路由功能,并把上面介紹到的三個陳述句處理器實體作為自身的委托物件而已,所以執行器在構建陳述句處理器時,都是直接 new 了 RoutingStatementHandler 實體,
// 1、執行器構建陳述句處理器實體
public StatementHandler newStatementHandler(...) {
// 構建路由陳述句處理器即可!
StatementHandler statementHandler =
new RoutingStatementHandler(...);
// 其它邏輯忽略...
return statementHandler;
}
// 2、實際構造方法(路由關系)
public RoutingStatementHandler(...) {
// 根據指定型別構造委托實體
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(...);
break;
case PREPARED:
delegate = new PreparedStatementHandler(...);
break;
case CALLABLE:
delegate = new CallableStatementHandler(...);
break;
default:
throw new ExecutorException(
"Unknown statement type: "
+ ms.getStatementType());
}
}
我們前面介紹了執行器具體執行 CRUD 操作時,構造的陳述句處理器默認使用 PrepareStatementHandler ,不過有些好奇的腦袋就想問問,那我們能不能指定陳述句處理器型別呢?
當然可以,例如我們指定更新用戶陳述句適用預編譯處理陳述句處理器:
<!--取值范圍 STATEMENT, PREPARED, CALLABLE -->
<update id="updateUser" statementType="STATEMENT">
update t_user set name = #{newName}
</update>
當 Mybatis 在決議映射器中的每條陳述句時,會設定陳述句處理器型別:
// 陳述句物件決議器
public class XMLStatementBuilder {
// 決議陳述句節點
public void parseStatementNode() {
// 設定陳述句處理器型別
// 默認是 PREPARED 型別
StatementType statementType =
StatementType.valueOf(
context.getStringAttribute(
"statementType",
StatementType.PREPARED.toString()
)
);
}
}
所以,陳述句執行器與資料庫的互動程序:

當然,陳述句處理器介面 StatementHandler 提供了基本介面,一般我們沒必要自定義實作類,所以可以簡單看一下即可:


9、ParamerHandler – 引數處理器
ParameterHandler 是一個引數處理器介面,它負責把用戶傳遞的引數轉換成 JDBC Statement 所需要的引數,底層做資料轉換的作業會交給型別轉換器 TypeHandler,后面會介紹,
很顯然,需要對傳入的引數進行轉換處理的 StatementHandler 實體只有兩個,分別是:
- PrepareStatementHandler 預編譯處理器
- CallableStatementHandler 存盤程序處理器
上面在介紹陳述句處理器的時候,我們有介紹說基礎陳述句處理器 BaseStatementHandler 在進行實體構建時,會同時構建引數處理器與結果集處理器,所以引數處理器就是在此時被構建,
// 基礎陳述句處理器
public abstract class BaseStatementHandler{
// 構造實體時
protected BaseStatementHandler(...){
// 其它邏輯可忽略...
// 1、構建引數處理器
this.parameterHandler =
conf.newParameterHandler(...);
// 2、構建結果集處理器
this.resultSetHandler =
conf.newResultSetHandler(...);
}
}
對于引數處理器介面,相對簡單,只有1個默認實作類 DefaultParameterHandler ,該介面只有兩個方法,分別是:

- 1、setParameters 設定引數,發生在 CURD 陳述句執行時,陳述句處理器設定引數
// 有2個處理器會使用,分別是:
// 預編譯處理器 PreparedStatementHandler
// 存盤程序處理器 CallableStatementHandler
public void parameterize(Statement statement) {
//使用ParameterHandler物件來完成對Statement的設值
parameterHandler.setParameters(statement);
}
應用場景例如查詢用戶物件時,設定姓名,引數處理器結合型別處理器把 name 屬性占位符進行賦值,
<select id="queryUSer">
select * from t_user where name = #{name}
</select>
- 2、getParameterObject 獲取引數,發生在結果集回傳時,結果集處理器獲取物件引數,值得注意的時,該方法只用于存盤程序處理器 CallableStatementHandler ,
// 默認結果集處理器
public class DefaultResultSetHandler{
// 處理輸出引數
public void handleOutputParameters(...) {
// 獲取引數
final Object parameterObject =
parameterHandler.getParameterObject();
// 其它存盤程序輸出引數處理邏輯...
}
}

10、ResultSetHandler – 結果集處理器
ResultSetHandler 是一個結果集處理器介面,它負責負責將 JDBC 回傳的結果集 resultSet 物件轉換為 List 型別的集合,是在陳述句處理器構建實體時被同時創建,底層做資料轉換的作業會交給型別轉換器 TypeHandler,它有1個默認實作類 DefaultResultSetHandler,該介面有3個方法,分別是:

- handleResultSets:負責結果集處理,完成映射回傳結果物件
- handleCursorResultSets :負責游標物件處理
- handleOutputParameters :負責存盤程序的輸出引數處理
結果集處理器對于 JDBC 回傳的結果集的基本處理,是先獲取我們在映射器 Mapper 中指定 resultType 或 resultMap 映射關系,然后遍歷決議結果集中的每一列資料,底層通過 MetaObject 物件做相關的反射處理,
對于詳細的原始碼邏輯,我們后續原始碼剖析部分詳細講,
不講不是中國人 O(∩_∩)O ~

11、TypeHandler – 型別轉換器
TypeHandler 是一個型別轉換器/處理器介面,它負責 Java 資料型別和 JDBC 資料型別之間的映射與轉換,當對 Statement 物件設定引數時,由 JavaType 轉換為 JdbcType,當對 Statement 回傳結果集進行封裝映射時,又會將 JdbcType 轉換為 JavaType,

一般,我們可以直接使用 Mybatis 內置的型別處理器,簡單看了一下有 65+ 個,當然我們是可以根據業務需要自定義型別處理器的,以便處理復雜型別或非標型別,
具體做法為:
1、實作 org.apache.ibatis.type.TypeHandler 介面;
2、繼承 org.apache.ibatis.type.BaseTypeHandler 類,
其中 BaseTypeHandler 類作為抽象類就已經實作了 TypeHandler 介面,
我們看到介面 TypeHandler 定義了四個方法:
public interface TypeHandler<T> {
// 設定引數
void setParameter(
PreparedStatement ps,
int i, T parameter,
JdbcType jdbcType);
// 根據列名獲取轉換結果
T getResult(ResultSet rs, String columnName);
// 根據列下標獲取轉換結果
T getResult(ResultSet rs, int columnIndex);
// 根據列下標獲取【存盤程序】的輸出結果
T getResult(CallableStatement cs, int columnIndex);
}
其實,我之前在介紹 Mybatis 核心配置的時候,有大力介紹過型別處理器,沒必要重復寫(其實是懶),感興趣的朋友可以直接看我們之前的文章 **《Mybatis系列全解(四):全網最全!Mybatis組態檔XML全貌詳解》**中對型別處理器 TypeHandler 的介紹,

12、MappedStatement – 陳述句物件
MappedStatement 陳述句物件,就是我們在映射器 Mapper 中維護的每一條陳述句,例如 <select|update|delete|insert>,Mybatis 中通過陳述句構造器 XMLStatementBuilder 對每一個陳述句進行決議:

整個決議程序分為4步驟:
- 1、配置決議器 XMLConfigBuilder 決議映射器:
// Configuration 配置決議器
public class XMLConfigBuilder{
// 決議映射器
private void mapperElement(){
// 創建映射器決議實體
XMLMapperBuilder mapperParser =
new XMLMapperBuilder(...);
// 開始決議
mapperParser.parse();
}
}
2、映射物件決議器 XMLMapperBuilder 決議陳述句
// 映射物件決議器
public class XMLMapperBuilder{
// 1、決議入口
public void parse() {
// 決議映射器檔案
configurationElement(
parser.evalNode("/mapper"));
}
// 2、節點決議
private void configurationElement(XNode context) {
// 構建陳述句物件
buildStatementFromContext(
context.evalNodes(
"select|insert|update|delete"));
}
// 3、最終呼叫陳述句決議器
private void buildStatementFromContext(){
// 創建陳述句決議實體
XMLStatementBuilder statementParser =
new XMLStatementBuilder();
// 決議陳述句節點
statementParser.parseStatementNode();
}
}
3、陳述句決議器 XMLStatementBuilder 決議每一個節點
// 陳述句決議器
public class XMLStatementBuilder{
// 決議陳述句節點
public void parseStatementNode() {
// 通過陳述句輔助類構建陳述句物件
builderAssistant.addMappedStatement(...)
}
}
4、陳述句輔助類 MapperBuilderAssistant 添加進陳述句集合中
// 陳述句輔助類
public class MapperBuilderAssistant{
// 添加陳述句物件
public MappedStatement addMappedStatement(
// 最終添加到配置類的陳述句集合中
configuration.addMappedStatement(statement);
}
}

13、SqlSource – SQL源
SqlSource 是一個 SQL 源介面,它會結合用戶傳遞的引數物件 parameterObject,動態地生成 SQL 陳述句,并最終封裝成 BoundSql 物件,SqlSource 介面有5個實作類,分別是:StaticSqlSource、DynamicSqlSource、RawSqlSource、ProviderSqlSource、VelocitySqlSource (這只是一個測驗用例,而非真正模板 Sql 源實作類),

- StaticSqlSource:靜態 SQL 源實作類,所有的 SQL 源最終都會構建 StaticSqlSource 實體,該實作類會生成最終可執行的 SQL 陳述句供 statement 或 prepareStatement 使用,
- RawSqlSource:原生 SQL 源實作類,決議構建含有 ‘#{}’ 占位符的 SQL 陳述句或原生 SQL 陳述句,決議完最侄訓構建 StaticSqlSource 實體,
- DynamicSqlSource:動態 SQL 源實作類,決議構建含有 ‘${}’ 替換符的 SQL 陳述句或含有動態 SQL 的陳述句(例如 If/Where/Foreach等),決議完最侄訓構建 StaticSqlSource 實體,
- ProviderSqlSource:注解方式的 SQL 源實作類,會根據 SQL 陳述句的內容分發給 RawSqlSource 或 DynamicSqlSource ,當然最終也會構建 StaticSqlSource 實體,
- VelocitySqlSource:模板 SQL 源實作類,官方申明這只是一個測驗用例,而非真正的模板 Sql 源實作類,
SqlSource 實體在配置類 Configuration 決議階段就被創建,Mybatis 框架會依據3個維度的資訊來選擇構建哪種資料源實體:
- 第一個維度:客戶端的 SQL 配置方式:XML 方式或者注解方式,
- 第二個維度:SQL 陳述句中是否使用動態 SQL ( if/where/foreach 等 ),
- 第三個維度:SQL 陳述句中是否含有替換符 ‘${}’ 或占位符 ‘#{}’ ,
SqlSource 介面只有一個方法 getBoundSql ,就是創建 BoundSql 物件,
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}

14、BoundSql – SQL陳述句
BoundSql 物件存盤了動態生成的 SQL 陳述句以及相應的引數資訊,BoundSql 物件是在執行器具體執行 CURD 時通過實際的 SqlSource 實體所構建,通過 BoundSql 能夠獲取到實際資料庫執行的 SQL 陳述句,系統可根據 SQL 陳述句構建 Statement 或者 PrepareStatement ,
public class BoundSql {
//該欄位中記錄了SQL陳述句,該SQL陳述句中可能含有"?"占位符
private final String sql;
//SQL中的引數屬性集合
private final List<ParameterMapping> parameterMappings;
//客戶端執行SQL時傳入的實際引數值
private final Object parameterObject;
//復制 DynamicContext.bindings 集合中的內容
private final Map<String, Object> additionalParameters;
//通過 additionalParameters 構建元引數物件
private final MetaObject metaParameters;
}

總結
本文整整2周才基本修整完善,順著 Mybatis 的資料庫核心執行流程,我們大致介紹了 Mybatis 中幾個相對核心的 API,我們是一邊構建核心架構功能設計圖示,一邊梳理 API 相關知識脈絡,目前基本算是捋清,
其中比較苦惱的問題就是,對于文章內容范圍尺度的把控,每篇文章我都希望講得全面,講得細致,這兩個堅持就注定了文章的內容不可能全是干貨,導致略懂的人只能選擇跳躍式閱讀,對于有營養的知識點需要挑挑揀揀,自我取舍;同時還會導致內容篇幅巨長,導致整體閱讀耗時過長,不易快速吸收,
后續嘗試改變一下寫作方式,盡量輸出干貨、內容點到為止,對于需要巨細剖析的內容,我們單立文章解讀,
本篇完,本系列下一篇我們講《 Mybatis系列全解(七):Dao層兩種實作方式 》,

文章持續更新,微信搜索「潘潘和他的朋友們」第一時間閱讀,隨時有驚喜,本文會在 GitHub https://github.com/JavaWorld 收錄,關于熱騰騰的技術、框架、面經、解決方案、摸魚技巧、教程、視頻、漫畫等等等等,我們都會以最美的姿勢第一時間送達,歡迎 Star ~ 我們未來 不止文章!想進讀者群的朋友歡迎撩我個人號:panshenlian,備注「加群」我們群里暢聊, BIU ~

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/247640.html
標籤:其他
