面試官:MyBatis 插件有什麼用途?說說底層原理?我竟然不會。。

來源:https://www.cnblogs.com/chenpi/p/10498921.html

背景

關於Mybatis插件,大部分人都知道,也都使用過,但很多時候,我們僅僅是停留在表面上,知道Mybatis插件可以在DAO層進行攔截,如打印執行的SQL語句日誌,做一些權限控制,分頁等功能;但對其內部實現機制,涉及的軟件設計模式,編程思想往往沒有深入的理解。

本篇案例將幫助讀者對Mybatis插件的使用場景,實現機制,以及其中涉及的編程思想進行一個小結,希望對以後的編程開發工作有所幫助。

注:本案例以mybatis 3.4.7-SNAPSHOT版本爲例。

PS:文章是挺久之前寫的,當時花了一些心思,存到電腦的word裏,今天正好看到,就是裏面的源碼都是圖片,哈哈哈,湊合着看吧。

Mybatis插件典型適用場景

分頁功能

mybatis的分頁默認是基於內存分頁的(查出所有,再截取),數據量大的情況下效率較低,不過使用mybatis插件可以改變該行爲,只需要攔截StatementHandler類的prepare方法,改變要執行的SQL語句爲分頁語句即可;

公共字段統一賦值

一般業務系統都會有創建者,創建時間,修改者,修改時間四個字段,對於這四個字段的賦值,實際上可以在DAO層統一攔截處理,可以用mybatis插件攔截Executor類的update方法,對相關參數進行統一賦值即可;

性能監控

對於SQL語句執行的性能監控,可以通過攔截Executor類的update, query等方法,用日誌記錄每個方法執行的時間;

其它

其實mybatis擴展性還是很強的,基於插件機制,基本上可以控制SQL執行的各個階段,如執行階段,參數處理階段,語法構建階段,結果集處理階段,具體可以根據項目業務來實現對應業務邏輯。

Mybatis插件介紹

什麼是Mybatis插件

與其稱爲Mybatis插件,不如叫Mybatis攔截器,更加符合其功能定位,實際上它就是一個攔截器,應用代理模式,在方法級別上進行攔截。

支持攔截的方法

  • 執行器Executor(update、query、commit、rollback等方法);
  • 參數處理器ParameterHandler(getParameterObject、setParameters方法);
  • 結果集處理器ResultSetHandler(handleResultSets、handleOutputParameters等方法);
  • SQL語法構建器StatementHandler(prepare、parameterize、batch、update、query等方法);

攔截階段

那麼這些類上的方法都是在什麼階段被攔截的呢?爲理解這個問題,我們先看段簡單的代碼(摘自mybatis源碼中的單元測試SqlSessionTest類),來了解下典型的mybatis執行流程,如下代碼所示:

以上代碼主要完成以下功能:

  • 讀取mybatis的xml配置文件信息
  • 通過SqlSessionFactoryBuilder創建SqlSessionFactory對象
  • 通過SqlSessionFactory獲取SqlSession對象
  • 執行SqlSession對象的selectList方法,查詢結果
  • 關閉SqlSession

如下是時序圖,在整個時序圖中,涉及到mybatis插件部分已標紅,基本上就是體現在上文中提到的四個類上,對這些類上的方法進行攔截。

Mybatis插件實現機制

插件配置信息的加載

先來看下mybatis是如何加載插件配置的,對應的xml配置信息如下:

對應的解析代碼如下,主要做以下工作:

  1. 根據解析到的類信息創建Interceptor對象;
  2. 調用setProperties方法設置屬性變量;
  3. 添加到Configuration的interceptorChain攔截器鏈中;

以上邏輯對應的時序圖如下:

代理對象的生成

Mybatis插件的實現機制主要是基於動態代理實現的,其中最爲關鍵的就是代理對象的生成,所以有必要來了解下這些代理對象是如何生成的。

Executor代理對象

ParameterHandler代理對象

ResultSetHandler代理對象

StatementHandler代理對象

觀察源碼,發現這些可攔截的類對應的對象生成都是通過InterceptorChain的pluginAll方法來創建的,進一步觀察pluginAll方法,如下:

遍歷所有攔截器,調用攔截器的plugin方法生成代理對象,注意生成代理對象重新賦值給target,所以如果有多個攔截器的話,生成的代理對象會被另一個代理對象代理,從而形成一個代理鏈條,執行的時候,依次執行所有攔截器的攔截邏輯代碼;

接下來看一下我們在編寫攔截器的時候,一個典型的plugin方法實現方式,如下:

再進一步查看wrap方法,如下:

典型的動態代理實現,調用的是Proxy.newProxyInstance方法來生成代理對象。

以上邏輯對應的時序圖如下,這裏我們假設聲明瞭兩個攔截器,那麼在創建target代理對象的時候,最終返回的代理對象proxy2,實際上代理了proxy1,而proxy1又代理了target,:

攔截邏輯的執行

由於真正去執行Executor、ParameterHandler、ResultSetHandler和StatementHandler類中的方法的對象是代理對象(建議將代理對象轉爲class文件,反編譯查看其結構,幫助理解),所以在執行方法時,首先調用的是Plugin類(實現了InvocationHandler接口)的invoke方法,如下:

首先根據執行方法所屬類獲取攔截器中聲明需要攔截的方法集合;

判斷當前方法需不需要執行攔截邏輯,需要的話,執行攔截邏輯方法(即Interceptor接口的intercept方法實現),不需要則直接執行原方法。

可以關注下Interceptor接口的intercept方法實現,一般需要用戶自定義實現邏輯,其中有一個重要參數,即Invocation類,通過改參數我們可以獲取執行對象,執行方法,以及執行方法上的參數,從而進行各種業務邏輯實現,一般在該方法的最後一句代碼都是invocation.proceed()(內部執行method.invoke方法),否則將無法執行下一個攔截器的intercept方法。

以上邏輯對應的時序圖如下,這裏我們以執行executor對象的query方法爲例,且假設有兩個攔截器存在:

Mybatis插件開發例子

這裏以分頁插件爲例,來了解下一般mybatis插件的編寫規則,如下所示:

主要需要實現三個方法

  1. intercept:在此實現自己的攔截邏輯,可從Invocation參數中拿到執行方法的對象,方法,方法參數,從而實現各種業務邏輯, 如下代碼所示,從invocation中獲取的statementHandler對象即爲被代理對象,基於該對象,我們獲取到了執行的原始SQL語句,以及prepare方法上的分頁參數,並更改SQL語句爲新的分頁語句,最後調用invocation.proceed()返回結果。
  2. plugin:生成代理對象;
  3. setProperties:設置一些屬性變量;

小結

簡單的說,mybatis插件就是對ParameterHandler、ResultSetHandler、StatementHandler、Executor這四個接口上的方法進行攔截,利用JDK動態代理機制,爲這些接口的實現類創建代理對象,在執行方法時,先去執行代理對象的方法,從而執行自己編寫的攔截邏輯,所以真正要用好mybatis插件,主要還是要熟悉這四個接口的方法以及這些方法上的參數的含義;

另外,如果配置了多個攔截器的話,會出現層層代理的情況,即代理對象代理了另外一個代理對象,形成一個代理鏈條,執行的時候,也是層層執行;

關於mybatis插件涉及到的設計模式和軟件思想如下:

  1. 設計模式:代理模式、責任鏈模式;
  2. 軟件思想:AOP編程思想,降低模塊間的耦合度,使業務模塊更加獨立;

一些注意事項:

  1. 不要定義過多的插件,代理嵌套過多,執行方法的時候,比較耗性能;
  2. 攔截器實現類的intercept方法裏最後不要忘了執行invocation.proceed()方法,否則多個攔截器情況下,執行鏈條會斷掉;

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這纔是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發布,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章