一篇文章告訴你JAVA Mybatis框架的核心原理到底有多重要
持久層的那些事
什么是 JDBC
JDBC(JavaDataBase Connectivity)就是 Java 數(shù)據(jù)庫連接, 說的直白點就是 使用 Java 語言操作數(shù)據(jù)庫
本來我們是通過控制臺或客戶端操作的數(shù)據(jù)庫, JDBC 是用 Java 語言來發(fā)送 SQL 語句
JDBC 原理
最初 SUN 公司希望提供一套 能夠適用所有數(shù)據(jù)庫的 API, 但是在實際操作中卻發(fā)現(xiàn)這是項基本不可能完成的任務(wù)
因為各個廠商所提供的 數(shù)據(jù)庫差異實在太大, 所以 SUN 公司與數(shù)據(jù)庫廠商討論出的就是:由 SUN 公司提供出一套訪問數(shù)據(jù)庫的規(guī)范 API, 并提供相對應(yīng)的連接數(shù)據(jù)庫協(xié)議標(biāo)準(zhǔn), 然后各廠商根據(jù)規(guī)范提供一套訪問自家數(shù)據(jù)庫的 API 接口
最終:SUN 公司提供的規(guī)范 API 稱之為 JDBC, 各廠商提供的自家數(shù)據(jù)庫 API 接口稱之為 驅(qū)動
什么是 Mybatis
Mybatis 是一款優(yōu)秀的 ORM(持久層)框架,使用 Java 語言 編寫前身是 apache 的一個開源項目 iBatis,2010 年遷移到 google code 并正式改名為 Mybatis ORM 持久層 指的是 : 將業(yè)務(wù)數(shù)據(jù)存儲到磁盤,也具備長期存儲能力,只要磁盤不損壞,如果在斷電情況下,重啟系統(tǒng)仍然可以讀取數(shù)據(jù)
Mybatis 與 JDBC 的關(guān)系
在沒有持久層框架之前, 想要代碼中操作數(shù)據(jù)庫都必須通過 JDBC 來操作, 接下來一個例子來說明兩者之間的關(guān)系
JDBC 操作數(shù)據(jù)庫
相信大家都在實際項目中使用過 Mybatis, 可以聯(lián)想一下, 平常我們工作中, 是否做過以下事情:
- 是否裝載過數(shù)據(jù)庫驅(qū)動?
- 是否從驅(qū)動中獲取數(shù)據(jù)庫連接?
- 是否創(chuàng)建過執(zhí)行 SQL 的 Statement?
- 是否自行將數(shù)據(jù)庫返回結(jié)果轉(zhuǎn)換成 Java 對象?
- 是否關(guān)閉過 finally 塊中的三個對象?
看到上面的靈魂拷問, 就可以對本次分享的第一個問題作出解答:
Mybatis 針對 JDBC 中重復(fù)操作做了封裝, 同時擴展并優(yōu)化部分功能
Mybatis 關(guān)鍵詞說明
📖 如果在閱讀文章前沒有接觸過 Mybatis 源碼相關(guān)的內(nèi)容, 建議將下述名詞多看幾遍再向下閱讀
SqlSession
負責(zé)執(zhí)行 select、insert、update、delete 等命令, 同時負責(zé)獲取映射器和管理事務(wù); 其底層封裝了與 JDBC 的交互, 可以說是 mybatis 最核心的接口之一
SqlSessionFactory
負責(zé)創(chuàng)建 SqlSession 的工廠, 一旦被創(chuàng)建就應(yīng)該在應(yīng)用運行期間一直存在, 不需要額外再進行創(chuàng)建
SqlSessionFactoryBuilder
主要是負責(zé)創(chuàng)建 SqlSessionFactory 的構(gòu)造器類, 其中使用到了構(gòu)建者設(shè)計模式; 僅負責(zé)創(chuàng)建 SqlSessionFactory
Configuration
Mybatis 最重要的配置類, 沒有之一, 存儲了大量的對象配置, 可以看源碼感受一下
MappedStatement
MappedStatement 是保存 SQL 語句的數(shù)據(jù)結(jié)構(gòu), 其中的類屬性都是由解析 .xml 文件中的 SQL 標(biāo)簽轉(zhuǎn)化而成
Executor
SqlSession 對象對應(yīng)一個 Executor, Executor 對象作用于 增刪改查方法 以及 事務(wù)、緩存 等操作
ParameterHandler
Mybatis 中的 參數(shù)處理器, 類關(guān)系比較簡單
StatementHandler
StatementHandler 是 Mybatis 負責(zé) 創(chuàng)建 Statement 的處理器, 根據(jù)不同的業(yè)務(wù)創(chuàng)建不同功能的 Statement
ResultSetHandler
ResultSetHandler 是 Mybatis 負責(zé)將 JDBC 返回數(shù)據(jù)進行解析, 并包裝為 Java 中對應(yīng)數(shù)據(jù)結(jié)構(gòu)的處理器
Interceptor
Interceptor 為 Mybatis 中定義公共攔截器的接口, 其中定義了相關(guān)實現(xiàn)方法
Mybatis 架構(gòu)設(shè)計
架構(gòu)圖
基礎(chǔ)支持層
反射模塊
反射在 Java 中的應(yīng)用可以說是相當(dāng)廣泛了, 同時也是一把雙刃劍。 Mybatis 框架本身 封裝出了反射模塊, 提供了比原生反射更 簡潔易用的 API 接口, 以及對 類的元數(shù)據(jù)增加緩存, 提高反射的性能
類型轉(zhuǎn)換
類型轉(zhuǎn)換模塊最重要的功能就是在為 SQL 語句綁定實參時, 將 Java 類型轉(zhuǎn)為 JDBC 類型, 在映射結(jié)果集時再由 JDBC 類型轉(zhuǎn)為 Java 類型
另外一個功能就是提供別名機制, 簡化了配置文件的定義
日志模塊
日志對于系統(tǒng)的作用不言而喻, 尤其是測試、生產(chǎn)環(huán)境上查看信息及排查錯誤等都非常重要。主流的日志框架包括 Log4j、Log4j2、S l f4j 等, Mybatis 的日志模塊作用就是 集成這些日志框架
資源加載
Mybatis 對類加載器進行了封裝, 用來確定類加載器的使用順序, 用來記載類文件以及其它資源文件, 感興趣可以參考 ClassLoaderWrapper
解析器模塊
解析器模塊主要提供了兩個功能, 一個是封裝了 XPath 類, 在 Mybatis 初始化時解析 Mybatis-config.xml 配置文件以及映射配置文件提供功能, 另一點就是處理動態(tài) SQL 語句的占位符提供幫助
核心處理層
配置解析
在 Mybatis 初始化時, 會加載 Mybatis-config.xml 文件中的配置信息, 解析后的配置信息會 轉(zhuǎn)換成 Java 對象添加到 Configuration 對象
📖 比如說在 .xml 中定義的 resultMap 標(biāo)簽, 會被解析為 ResultMap 對象
SQL 解析
大家如果手動拼寫過復(fù)雜 SQL 語句, 就會明白會有多痛苦。Mybatis 提供出了動態(tài) SQL, 加入了許多判斷循環(huán)型標(biāo)簽, 比如 : if、where、foreach、set 等, 幫助開發(fā)者節(jié)約了大量的 SQL 拼寫時間 SQL 解析模塊的作用就是將 Mybatis 提供的動態(tài) SQL 標(biāo)簽解析為帶占位符的 SQL 語句, 并在后期將實參對占位符進行替換
SQL 執(zhí)行
SQL 的執(zhí)行過程涉及幾個比較重要的對象, Executor
、StatementHandler
、ParameterHandler
、ResultSetHandler
Executor
負責(zé)維護 一級、二級緩存以及事務(wù)提交回滾操作, 舉個查詢的例子, 查詢請求會由 Executor 交給 StatementHandler 完成
StatementHandler
通過ParameterHandler
完成SQL
語句的實參綁定, 通過java.sql.Statement
執(zhí)行 SQL
語句并拿到對應(yīng)的 結(jié)果集映射
最后交由 ResultSetHandler 對結(jié)果集進行解析, 將 JDBC 類型轉(zhuǎn)換為程序自定義的對象
插件
插件模塊是 Mybatis 提供的一層擴展, 可以針對 SQL 執(zhí)行的四大對象進行 攔截并執(zhí)行自定義插件插件編寫需要很熟悉 Mybatis 運行機制, 這樣才能控制編寫的插件安全、高效
接口層
接口層只是 Mybatis
提供給調(diào)用端的一個接口 SqlSession
, 調(diào)用端在進行調(diào)用接口中方法時, 會調(diào)用核心處理層相對應(yīng)的模塊來完成數(shù)據(jù)庫操作
問題答疑
.xml 文件定義 Sql 語句如何解析
Mybatis 在創(chuàng)建 SqlSessionFactory 時, XMLConfigBuilder 會解析 Mybatis-config.xml 配置文件
Mybatis 相關(guān)解析器
Mybatis 解析器模塊中定義了相關(guān)解析器的抽象類 BaseBuilder, 不同的子類負責(zé)實現(xiàn)解析不同的功能, 使用了 Builder 設(shè)計模式
XMLConfigBuilder 負責(zé)解析 mybatis-config.xml 配置文件
XMLMapperBuilder 負責(zé)解析業(yè)務(wù)產(chǎn)生的 xxxMapper.xml
mybatis-config.xml 解析
XMLConfigBuilder 解析 mybatis-config.xml 內(nèi)容參考代碼 :
XMLConfifigBuilder#parseConfiguration()
方法將 mybatis-config.xml
中定義的標(biāo)簽進行相關(guān)解析并填充到Configuration
對象中
xxxMapper.xml 解析
XMLConfifigBuilder#mapperElement()
中解析配置的 mappers 標(biāo)簽, 找到具體的.xml
文件, 并將其中的select
、insert
、update
、delete
、resultMap
等標(biāo)簽解析為 Java 中的對象信息具體解析 xxxMapper.xml
的對象為 XMLMapperBuilder
, 具體的解析方法為parse()
Mybatis
創(chuàng)建 SqlSessionFactory
會解析mybatis-config.xml,
然后 解析configuration 標(biāo)簽下的子標(biāo)簽, 解析 mappers
標(biāo)簽時, 會根據(jù)相關(guān)配置讀取到 .xml
文件, 繼而解析 .xml
中各個標(biāo)簽具體的 select
、insert
、update
、delete
標(biāo)簽定義為 MappedStatement
對象, .xml
文件中的其余標(biāo)簽也會根據(jù)不同映射解析為 Java 對象
MappedStatement
這里重點說明下 MappedStatement 對象, 一起看一下類中的屬性和 SQL 有何關(guān)聯(lián)呢
MappedStatement 對象中 提供的屬性與 .xml 文件中定義的 SQL 語句 是能夠?qū)?yīng)上的, 用來 控制每條 SQL 語句的執(zhí)行行為
Mapper 接口的存儲與實現(xiàn)
在平常我們寫的 SSM 框架中, 定義了 Mapper 接口與 .xml 對應(yīng)的 SQL 文件, 在 Service 層直接注入 xxxMapper 就可以了
也沒有看到像 JDBC 操作數(shù)據(jù)庫的操作, Mybatis 在中間是如何為我們省略下這些重復(fù)繁瑣的操作呢
這里使用 Mybatis 源碼中的測試類進行驗證, 首先定義 Mapper 接口, 省事直接注解定義 SQL
這里使用 SqlSession 來獲取 Mapper 操作數(shù)據(jù)庫, 測試方法如下
創(chuàng)建 SqlSession
#1 從 SqlSessionFactory 中打開一個 新的 SqlSession
獲取 Mapper 實例
#2 就存在一個疑問點, 定義的 AutoConstructorMapper 明明是個接口, 為什么可以實例化為對象?
動態(tài)代理方法調(diào)用
#3 通過創(chuàng)建的對象調(diào)用類中具體的方法, 這里具體聊一下 #2 操作
SqlSession 是一個接口, 有一個 默認的實現(xiàn)類 DefaultSqlSession, 類中包含了 Configuration 屬性
Mapper 接口的信息以及 .xml
中SQL語句是在Mybatis
初始化時添加 到 Configuration
的 MapperRegistry
屬性中的
#2中的 getMapper 就是從 MapperRegistry 中獲取 Mapper
看一下 MapperRegistry 的類屬性都有什么
config 為 保持全局唯一 的 Configuration 對象引用
knownMappers 中 Key-Class 是 Mapper 對象, Value-MapperProxyFactory 是通過 Mapper 對象衍生出的 Mapper 代理工廠
再看一下 MapperProxyFactory 類的結(jié)構(gòu)信息
mapperInterface
屬性是 Mapper
對象的引用, methodCache
的 key
是 Mapper
中的方法, value
是 Mapper
解析對應(yīng) SQL
產(chǎn)生的 MapperMethod
📖 Mybatis 設(shè)計 methodCache 屬性時使用到了 懶加載機制, 在初始化時不會增加對應(yīng) Method, 而是在 第一次調(diào)用時新增
MapperMethod 運行時數(shù)據(jù)如下, 比較容易理解
通過一個實際例子幫忙理解一下 MapperRegistry 類關(guān)系, Mapper 初始化第一次調(diào)用的對象狀態(tài), 可以看到 methodCache 容量為0
我們目前已經(jīng)知道 MapperRegistry
的類關(guān)系, 回頭繼續(xù)看一下第二步的 MapperRegistry#getMapper()
處理步驟
核心處理在 MapperProxyFactory#newInstance()
方法中, 繼續(xù)跟進
MapperProxy
繼承了 InvocationHandler
接口, 通過 newInstance()
最終返回的是由 Java Proxy 動態(tài)代理返回的動態(tài)代理實現(xiàn)類
看到這里就清楚了步驟二中接口為什么能夠被實例化, 返回的是 接口的動態(tài)代理實現(xiàn)類
Mybatis Sql 的執(zhí)行過程
根據(jù) Mybatis SQL 執(zhí)行流程圖進一步了解
大致可以分為以下幾步操作:
📖 在前面的內(nèi)容中, 知道了Mybatis Mapper 是動態(tài)代理的實現(xiàn), 查看 SQL 執(zhí)行過程, 就需要緊跟實現(xiàn)InvocationHandler 的MapperProxy類
執(zhí)行增刪改查
@Select(" SELECT * FROM SUBJECT WHERE ID = #{id}") PrimitiveSubject getSubject(@Param("id") final int id);
我們以上述方法舉例, 調(diào)用方通過 SqlSession
獲取Mapper
動態(tài)代理對象, 執(zhí)行Mapper
方法時會通過InvocationHandler
進行代理
在MapperMethod#execute
中, 根據(jù) MapperMethod
-> SqlCommand
-> SqlCommandType
來確定增、刪、改、查方法
📖 SqlCommandType 是一個枚舉類型, 對應(yīng)五種類型 UNKNOWN、INSERT、UPDATE、DELETE、SELECT、FLUSH
參數(shù)處理
查詢操作對應(yīng) SELECT 枚舉值, if else 中判斷為返回值是否集合、無返回值、單條查詢等, 這里以查詢單條記錄作為入口
Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param);
📖 這里能夠解釋一個之前困擾我的問題, 那就是為什么方法入?yún)⒅挥袉蝹€ @Param("id"), 但是參數(shù) param 對象會存在兩個鍵值對
繼續(xù)查看 SqlSession#selectOne 方法, sqlSession 是一個接口, 具體還是要看實現(xiàn)類 DefaultSqlSession
因為單條和查詢多條以及分頁查詢都是走的一個方法, 所以在查詢的過程中, 會將分頁的參數(shù)進行添加
執(zhí)行器處理
在 Mybatis 源碼中, 創(chuàng)建的執(zhí)行器默認是 CachingExecutor, 使用了裝飾者模式, 在類中保持了 Executor 接口的引用, CachingExecutor 在持有的執(zhí)行器基礎(chǔ)上增加了緩存的功能
delegate.query 就是在具體的執(zhí)行器了, 默認 SimpleExecutor, query 方法統(tǒng)一在抽象父類 BaseExecutor 中維護
BaseExecutor#queryFromDatabase 方法執(zhí)行了緩存占位符以及執(zhí)行具體方法, 并將查詢返回數(shù)據(jù)添加至緩存
BaseExecutor#doQuery 方法是由具體的 SimpleExecutor 實現(xiàn)
執(zhí)行 SQL
因為我們 SQL 中使用了參數(shù)占位符, 使用的是 PreparedStatementHandler 對象, 執(zhí)行預(yù)編譯SQL的 Handler, 實際使用 PreparedStatement 進行 SQL 調(diào)用
返回數(shù)據(jù)解析
將 JDBC 返回類型轉(zhuǎn)換為 Java 類型, 根據(jù) resultSets 和 resultMap 進行轉(zhuǎn)換
Mybatis 中分頁如何實現(xiàn)
通過 Mybatis 執(zhí)行分頁 SQL 有兩種實現(xiàn)方式, 一種是編寫 SQL 時添加 LIMIT, 一種是全局處理
SQL 分頁
<select id="getSubjectByPage" resultMap="resultAutoMap"> SELECT * FROM SUBJECT LIMIT #{CURRINDEX} , #{PAGESIZE} </select>
攔截器分頁
上文說到, Mybatis
支持了插件擴展機制, 可以攔截到具體對象的方法以及對應(yīng)入?yún)⒓墑e
我們添加插件時需要實現(xiàn)Interceptor
接口, 然后將插件寫在 mybatis-config.xml
配置文件中或者添加相關(guān)注解, Mybatis
初始化時解析才能在項目啟動時添加到插件容器中
由一個 List 結(jié)構(gòu)存儲項目中全部攔截器, 通過Configuration#addInterceptor 方法添加
重點需要關(guān)注 Interceptor#pluginAll 中 plugin 方法, Interceptor 只是一個接口, plugin 方法只能由其實現(xiàn)類完成
Plugin 可以理解為是一個工具類, Plugin#wrap 返回的是一個動態(tài)代理類
這里使用一個測試的 Demo 看一下方法運行時的參數(shù)
雖然是隨便寫的 Demo, 但是與正式使用的插件并無實際區(qū)別
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
關(guān)于@ComponentScan?TypeFilter自定義指定掃描bean的規(guī)則
這篇文章主要介紹了關(guān)于@ComponentScan?TypeFilter自定義指定掃描bean的規(guī)則,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09Java8特性之用Stream流代替For循環(huán)操作詳解
這篇文章主要介紹了Stream流代替For循環(huán)進行輸出,這樣可以使代碼更簡潔,希望對大家有所幫助。一起跟隨小編過來看看吧2021-09-09springboot的SpringPropertyAction事務(wù)屬性源碼解讀
這篇文章主要介紹了springboot的SpringPropertyAction事務(wù)屬性源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-11-11