深入詳解Mybatis的架構(gòu)原理與六大核心流程
MyBatis 是 Java 生態(tài)中非常著名的一款 ORM 框架,目前在一線互聯(lián)網(wǎng)大廠中應(yīng)用廣泛,Mybatis已經(jīng)成為了一個(gè)必會(huì)框架。
如果你想要進(jìn)入一線大廠,能夠熟練使用 MyBatis 開(kāi)發(fā)已經(jīng)是一項(xiàng)非常基本的技能,同時(shí)大廠也更希望自己的開(kāi)發(fā)人員深入了解 MyBatis 框架的原理和核心實(shí)現(xiàn)。
從這個(gè)角度看,理解 MyBatis 原理,閱讀 MyBatis核心源碼,這樣更有利于提高職場(chǎng)競(jìng)爭(zhēng)力。
在深入了解Mybatis的源碼之前,我們先了解一下Mybatis的整體架構(gòu)和工作原理,這樣有助于我們?cè)陂喿x源碼過(guò)程中了解思路和流程。
Mybatis架構(gòu)設(shè)計(jì)
我們把Mybatis的功能架構(gòu)分為三層:
- API接口層
- 數(shù)據(jù)處理層
- 基礎(chǔ)支撐層
接口層
接口層:主要就是和數(shù)據(jù)庫(kù)交互,提供給外部使用的接口API,開(kāi)發(fā)人員通過(guò)這些本地API來(lái)操縱數(shù)據(jù)庫(kù),接口層一接收到調(diào)用請(qǐng)求就會(huì)調(diào)用數(shù)據(jù)處理層來(lái)完成具體的數(shù)據(jù)處理。
以使用Mapper接口為例,將配置文件中的每一個(gè) 節(jié)點(diǎn)抽象為一個(gè) Mapper 接口,這個(gè)接口中聲明的方法和跟Mapper.xml中的 節(jié)點(diǎn)項(xiàng)對(duì)應(yīng)。
id值對(duì)應(yīng)方法名稱,parameterType 值對(duì)應(yīng)方法的入?yún)㈩愋停鴕esultMap 值則對(duì)應(yīng)返回值類型。
配置好后,MyBatis 會(huì)根據(jù)接口聲明的方法信息,通過(guò)動(dòng)態(tài)代理機(jī)制生成一個(gè)Mapper 實(shí)例,當(dāng)調(diào)用接口方法時(shí),根據(jù)這個(gè)方法的方法名和參數(shù)類型,確定Statement Id,底層還是通過(guò) SqlSession.select/update( “statementId”, parameter) 等來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的操作。
數(shù)據(jù)處理層
數(shù)據(jù)處理層:可以說(shuō)是MyBatis 的核心,負(fù)責(zé)具體的SQL查找、SQL解析、SQL執(zhí)行和執(zhí)行結(jié)果映射處理等,它主要的目的是根據(jù)調(diào)用的請(qǐng)求完成一次數(shù)據(jù)庫(kù)操作。
從大的方面上講,它要完成兩個(gè)功能:
- 通過(guò)傳入?yún)?shù)構(gòu)建動(dòng)態(tài)SQL語(yǔ)句
- SQL語(yǔ)句的執(zhí)行以及封裝查詢結(jié)果集
1.通過(guò)傳入?yún)?shù)構(gòu)建動(dòng)態(tài)SQL語(yǔ)句
動(dòng)態(tài)語(yǔ)句生成能夠說(shuō)是MyBatis框架很是優(yōu)雅的一個(gè)設(shè)計(jì),MyBatis 經(jīng)過(guò)傳入的參數(shù)值,使用 Ognl 來(lái)動(dòng)態(tài)地構(gòu)造SQL語(yǔ)句,使得MyBatis 有很強(qiáng)的靈活性和擴(kuò)展性。
參數(shù)映射指的是對(duì)于java 數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的轉(zhuǎn)換,這里有包括兩個(gè)過(guò)程:
- 查詢階段
- 查詢結(jié)果集轉(zhuǎn)換階段
查詢階段要將java類型的數(shù)據(jù),轉(zhuǎn)換成jdbc類型的數(shù)據(jù),經(jīng)過(guò) preparedStatement.setXXX() 來(lái)設(shè)值;
另外一個(gè)就是對(duì)resultset查詢結(jié)果集的jdbcType 數(shù)據(jù)轉(zhuǎn)換成java 數(shù)據(jù)類型。
2. SQL語(yǔ)句的執(zhí)行以及封裝查詢結(jié)果集
動(dòng)態(tài)SQL語(yǔ)句生成以后,MyBatis 將執(zhí)行SQL語(yǔ)句,并將可能返回的結(jié)果集轉(zhuǎn)換成List 列表。
MyBatis 在對(duì)結(jié)果集的處理中,支持結(jié)果集關(guān)系一對(duì)多和多對(duì)一的轉(zhuǎn)換,而且有兩種支持方式,一種為嵌套查詢語(yǔ)句的查詢,還有一種是嵌套結(jié)果集的查詢。
基礎(chǔ)支撐層
基礎(chǔ)支撐層是整個(gè)MyBatis框架的地基,負(fù)責(zé)最基礎(chǔ)的功能支撐,包括連接管理、事務(wù)管理、配置加載和緩存處理,這些都是共用的東西,將他們抽取出來(lái)作為最基礎(chǔ)的組件,為上層的數(shù)據(jù)處理層提供最基礎(chǔ)的支撐。
1.緩存機(jī)制
數(shù)據(jù)庫(kù)是實(shí)踐生成中非常核心的存儲(chǔ),很多業(yè)務(wù)數(shù)據(jù)都會(huì)落地到數(shù)據(jù)庫(kù),所以數(shù)據(jù)庫(kù)性能的優(yōu)劣直接影響了上層業(yè)務(wù)系統(tǒng)的優(yōu)劣。
我們很多線上業(yè)務(wù)都是讀多寫(xiě)少的場(chǎng)景,在數(shù)據(jù)庫(kù)遇到瓶頸時(shí),緩存是最有效、最常用的手段之一,正確使用緩存可以將一部分?jǐn)?shù)據(jù)庫(kù)請(qǐng)求攔截在緩存這一層,這就能夠減少一部分?jǐn)?shù)據(jù)庫(kù)的壓力,提高系統(tǒng)性能。
除了使用 Redis、Memcached 等外置的第三方緩存以外,持久化框架一般也會(huì)自帶內(nèi)置的緩存,例如,MyBatis 就提供了一級(jí)緩存和二級(jí)緩存,具體實(shí)現(xiàn)位于基礎(chǔ)支撐層的緩存模塊中。
2.反射工具
該模塊對(duì) Java 原生的反射進(jìn)行了良好的封裝,提供了更加簡(jiǎn)潔易用的 API ,方便上層使調(diào)用,并且對(duì)反射操作進(jìn)行了一系列優(yōu)化,例如緩存了類的元數(shù)據(jù),提高了反射操作的性能。
3.類型轉(zhuǎn)換
類型轉(zhuǎn)換模塊提供了兩個(gè)主要功能,一個(gè)功能是別名機(jī)制,MyBatis 為了簡(jiǎn)化配置文件提供了別名機(jī)制。
另一個(gè)功能是實(shí)現(xiàn) JDBC 類型與 Java 類型之間的轉(zhuǎn)換,該功能在為 SQL 語(yǔ)句綁定實(shí)參以及映射查詢結(jié)果集時(shí)都會(huì)涉及。
4.日志
提供詳細(xì)的日志輸出信息,并且能夠集成多種日志框架,其日志模塊的一個(gè)主要功能就是集成第三方日志框架。
5.資源加載
資源加載模塊主要是對(duì)類加載器進(jìn)行封裝,確定類加載器的使用順序,并提供了加載類文件以及其他資源文件的功能。
6.解析器
解析器模塊主要提供兩個(gè)功能,一個(gè)功能是對(duì) XPath 進(jìn)行封裝,為 MyBatis 初始化時(shí)解析 mybatis-config.xml 配置文件以及映射配置文件提供支持。
另一個(gè)功能是為處理動(dòng)態(tài) SQL 語(yǔ)句中的占位符提供支持。
7.事務(wù)管理
持久層框架一般都會(huì)提供一套事務(wù)管理機(jī)制實(shí)現(xiàn)數(shù)據(jù)庫(kù)的事務(wù)控制,MyBatis 對(duì)數(shù)據(jù)庫(kù)中的事務(wù)進(jìn)行了一層簡(jiǎn)單的抽象,提供了簡(jiǎn)單易用的事務(wù)接口和實(shí)現(xiàn)。
一般情況下,Java 項(xiàng)目都會(huì)集成 Spring,并由 Spring 框架管理事務(wù)。
8.Binding
在調(diào)用 SqlSession 相應(yīng)方法執(zhí)行數(shù)據(jù)庫(kù)操作時(shí),需要指定映射文件中定義的 SQL 節(jié)點(diǎn),如果出現(xiàn)拼寫(xiě)錯(cuò)誤,我們只能在運(yùn)行時(shí)才能發(fā)現(xiàn)相應(yīng)的異常。
為了盡早發(fā)現(xiàn)這種錯(cuò)誤,MyBatis 通過(guò) Binding 模塊將用戶自定義的 Mapper 接口與映射配置文件關(guān)聯(lián)起來(lái),系統(tǒng)可以通過(guò)調(diào)用自定義 Mapper 接口中的方法執(zhí)行相應(yīng)的SQL 語(yǔ)句完成數(shù)據(jù)庫(kù)操作,從而避免上述問(wèn)題。
需要注意的是,無(wú)須編寫(xiě)自定義 Mapper 接口的實(shí)現(xiàn),具體的Mapper接口實(shí)現(xiàn)MyBatis 會(huì)自動(dòng)為其創(chuàng)建動(dòng)態(tài)代理對(duì)象。
9.數(shù)據(jù)源
對(duì)于 ORM 框架而言,數(shù)據(jù)源的組織是一個(gè)非常重要的一部分,這直接影響到框架的性能問(wèn)題。
數(shù)據(jù)庫(kù)連接是一項(xiàng)有限的昂貴資源,一個(gè)數(shù)據(jù)庫(kù)連接對(duì)象均對(duì)應(yīng)一個(gè)物理數(shù)據(jù)庫(kù)連接,每次操作都打開(kāi)一個(gè)物理連接,使用完都關(guān)閉連接,這樣造成系統(tǒng)的性能低下。
數(shù)據(jù)庫(kù)連接池的解決方案是在應(yīng)用程序啟動(dòng)時(shí)建立足夠的數(shù)據(jù)庫(kù)連接,并將這些連接組成一個(gè)連接池,由應(yīng)用程序動(dòng)態(tài)地對(duì)池中的連接進(jìn)行申請(qǐng)、使用和釋放。
打開(kāi)Mybatis源碼找到datasource包下就可以看到連接池的實(shí)現(xiàn),如下圖所示:
Mybatis核心執(zhí)行流程
mybatis的總體執(zhí)行流程,總體如下六大步驟:
1.MyBatis配置文件
config.xml:配置了全局配置文件,配置了MyBatis的運(yùn)行環(huán)境等信息。
mapper,xml:sql的映射文件,配置了操作數(shù)據(jù)庫(kù)的sql語(yǔ)句,此文件需在config.xml中加載。
2.SqlSessionFactory
通過(guò)MyBatis環(huán)境等配置信息構(gòu)造SqlSessionFactory(會(huì)話工廠)。
3.SqlSession
通過(guò)會(huì)話工廠創(chuàng)建SqlSession(會(huì)話),對(duì)數(shù)據(jù)庫(kù)進(jìn)行增刪改查操作。
4.Exector執(zhí)行器
MyBatis底層自定義了Exector執(zhí)行器接口來(lái)具體操作數(shù)據(jù)庫(kù),Exector接口有兩個(gè)實(shí)現(xiàn),一個(gè)基本執(zhí)行器(默認(rèn)),一個(gè)是緩存執(zhí)行器,SqlSession底層是通過(guò)Exector接口操作數(shù)據(jù)庫(kù)。
5.MappedStatement
MyBatis的一個(gè)底層封裝對(duì)象,它包裝了MyBatis配置信息與sql映射信息等。mapper.xml中的insert/select/update/delete標(biāo)簽對(duì)應(yīng)一個(gè)MappedStatement對(duì)象。標(biāo)簽的id就是MappedStatement的id。
MappedStatement對(duì)sql執(zhí)行輸入?yún)?shù)進(jìn)行定義,包括HashMap、基本類型、pojo、Executor通過(guò)MappedStatement在執(zhí)行sql前將輸入的Java對(duì)象映射至sql中,輸入?yún)?shù)映射就是JDBC編程對(duì)preparedStatement設(shè)置參數(shù)。
MappedStatement對(duì)sql執(zhí)行輸出結(jié)果進(jìn)行定義,包括HashMap、基本類型、pojo,Executor通過(guò)MappedStatement在執(zhí)行sql后將輸出結(jié)果映射至Java對(duì)象中,輸出結(jié)果映射就是JDBC編程對(duì)結(jié)果的解析處理過(guò)程。
到此我就把Mybatis的架構(gòu)從全局上做了一個(gè)拆解,后續(xù)我將重點(diǎn)分析其核心源碼,這樣先全局再局部,這樣更有利于掌握其核心原理實(shí)現(xiàn),希望這個(gè)框架系列能對(duì)你有所用。