MyBatis 插件原理與實戰
前言
大家好,我是田螺。
最近在做新項目,基於若依(前後端分離版本)做的,他也使用了常用的分頁插件PageHelper
。
老規矩,今天文章還是分三步走,先上文章導讀,然後講原理,最後講解源碼案例。
最後達到的效果就是希望讀者朋友們在看完我寫的這篇文章後,能夠秒懂別人寫的 MyBatis 插件並且能夠開發出自己的 MyBatis 的插件。
文章導讀
MyBatis 插件原理與實戰
什麼是插件?
插件就是在具體的執行流程插一腳(觸發點、攔截器)來實現具體的功能。
一般插件會對執行流程中的上下文有依賴,抽象的說,我們也可以把 MyBatis 看作是 JDBC 的插件,只是功能越來來多,越來越強大,最後我們給了他一個新名字,叫做框架。
不管怎樣,JDBC 的那一套還是不會變的,只是做了抽象、封裝、歸類等。
要想知道插件的原理,首先就要對它的執行流程有一定的把控。
執行流程
前邊我們講到,MyBatis 是對 JDBC 的抽象、封裝。
我們首先來回顧一下 JDBC 的執行流程。
JDBC 執行流程
-
註冊驅動;
-
獲取 Connection 連接;
-
執行預編譯;
-
執行 SQL;
-
封裝結果集;
-
釋放資源;
給段僞代碼通透理解下:
// 註冊驅動
Class.forName("com.mysql.jdbc.Driver");
// 獲取鏈接
Connection con = DriverManager.getConnection(url, username, password);
// 執行預編譯
Statement stmt = con.createStatement();
// 執行SQL
ResultSet rs = stmt.executeQuery("SELECT * FROM ...");
// 封裝結果
while (rs.next()) {
String name = rs.getString("name");
String pass = rs.getString(1); // 此方法比較高效
}
// 釋放資源
if (rs != null) { // 關閉記錄集
}
if (stmt != null) { // 關閉聲明
}
if (conn != null) { // 關閉連接對象
}
上邊的代碼是不是很熟悉,我相信每個入門寫 Java 代碼的人,都寫過這段代碼。
緊接着,我們繼續來了解 MyBatis 的執行流程。
MyBatis 執行流程
-
讀取 MyBatis 的核心配置文件;
-
加載映射文件;
-
構造會話工廠獲取 SqlSessionFactory;
-
創建會話對象 SqlSession;
-
Executor 執行器;
-
MappedStatement 對象;
-
輸入參數映射;
-
封裝結果集;
上邊的文字可能不太好理解,我這裏也畫一幅執行流程圖,來方便理解。
MyBatis 執行流程
有沒有覺得 MyBatis 的執行流程和 JDBC 的執行流程主幹也差不多,只是在主幹過程中,把一些配置(mybatis-config.xml)、常用的定義文件單獨抽離出來(mapper.xml)和一些附帶擴展性的攔截點抽離了出來。
下面着重講一講我們的攔截點,因爲插件就是基於我們的攔截點來做的擴展。
攔截點
結合上邊的 MyBatis 執行流程,看下圖的各個攔截點:
MyBatis 攔截點
文字描述,MyBatis 允許使用插件來攔截的方法調用包括:
-
Executor:
攔截執行器的方法 (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed),Mybatis 的內部執行器,它負責調用 StatementHandler 操作數據庫,並把結果集通過 ResultSetHandler 進行自動映射,另外,他還處理了二級緩存的操作。從這裏可以看出,我們也是可以通過插件來實現自定義的二級緩存的;
-
ParameterHandler:
攔截參數的處理 (getParameterObject, setParameters) ,Mybatis 直接和數據庫執行 sql 腳本的對象。另外它也實現了 Mybatis 的一級緩存。這裏,我們可以使用插件來實現對一級緩存的操作 (禁用等等);
-
ResultSetHandler:
攔截結果集的處理 (handleResultSets, handleOutputParameters) ,Mybatis 實現 Sql 入參設置的對象。插件可以改變我們 Sql 的參數默認設置;
-
StatementHandler:
攔截 Sql 語法構建的處理 (prepare, parameterize, batch, update, query) ,Mybatis 把 ResultSet 集合映射成 POJO 的接口對象。我們可以定義插件對 Mybatis 的結果集自動映射進行修改。
攔截器爲什麼能夠攔截
org.apache.ibatis.session.Configuration
是在 MyBatis 初始化配置的類。
其中的newParameterHandler
、newResultSetHandler
、newStatementHandler
、newExecutor
這幾個方法在創建指定的對象(newParameterHandler 創建 ParameterHandler、newResultSetHandler 創建 ResultSetHandler、newStatementHandler 創建 StatementHandler、newExecutor 創建 Executor)對象的時候,都會調用一個統一的方法:
創建對象
這 4 個方法實例化了對應的對象之後,都會調用 interceptorChain 的 pluginAll 方法,那麼下面我們在來看 pluginAll 幹了什麼。
包路徑:org.apache.ibatis.plugin.InterceptorChain
public Object pluginAll(Object target) {
Interceptor interceptor;
for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
interceptor = (Interceptor)var2.next();
}
return target;
}
原來這個 pluginAll 方法就是遍歷所有的攔截器,然後順序執行我們插件的 plugin 方法, 一層一層返回我們原對象 (Executor/ParameterHandler/ResultSetHander/StatementHandler) 的代理對象。當我們調用四大接口對象的方法時候,實際上是調用代理對象的響應方法,代理對象又會調用四大接口對象的實例。
這裏我們看到所有的攔截器 Interceptor,其實它和我們平常寫代碼一樣,也是多態的利用,存在一個攔截器 Interceptor 接口,我們在實現插件的時候,也實現這個接口,就會被調用。
Interceptor 接口
包路徑:org.apache.ibatis.plugin
public interface Interceptor {
Object intercept(Invocation var1) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
}
}
這個接口只聲明瞭三個方法:
-
setProperties 方法是在 Mybatis 進行配置插件的時候可以配置自定義相關屬性,即:接口實現對象的參數配置;
-
plugin 方法是插件用於封裝目標對象的,通過該方法我們可以返回目標對象本身,也可以返回一個它的代理,可以決定是否要進行攔截進而決定要返回一個什麼樣的目標對象,官方提供了示例:return Plugin.wrap(target, this);
-
intercept 方法就是要進行攔截的時候要執行的方法;
編寫簡單的 MyBatis 插件
注:MyBatis 默認沒有一個攔截器接口的實現類,開發者可以實現符合自己需求的攔截器
@Intercepts({@Signature(type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
全局 xml 配置(實例化 bean)
<plugins>
<plugin interceptor="org.format.mybatis.cache.interceptor.ExamplePlugin"></plugin>
</plugins>
這個攔截器攔截 Executor 接口的 update 方法(其實也就是 SqlSession 的新增,刪除,修改操作),所有執行 executor 的 update 方法都會被該攔截器攔截到,就在裏邊做相對應的邏輯處理就可以了。
總結
今天這篇文章到這裏結束了,講解了什麼是插件首先需要了解執行流程,然後回顧我們的 JDBC 流程來推導出 MyBatis 的執行流程,通過初始化的org.apache.ibatis.session.Configuration
爲切入點,跟蹤到 interceptorChain 的 pluginAll 方法;最後通過一個簡單的插件來實操了一波。
參考鏈接:
https://zhuanlan.zhihu.com/p/150008843
https://blog.csdn.net/weixin_44046437/article/details/100523028
https://blog.csdn.net/weixin_44046437/article/details/100526643
程序員田螺 專注分享後端面試題,包括計算機網絡、MySql 數據庫、Redis 緩存、操作系統、Java 後端、大廠面試真題等領域。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/avDrr1dM-YjW0igfZ2RH6Q