mybatis實(shí)戰(zhàn)之?dāng)r截器解讀
mybatis實(shí)戰(zhàn)之?dāng)r截器
在服務(wù)的開發(fā)過程中,往往存在這樣的需求,針對(duì)業(yè)務(wù),實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)操作語(yǔ)句做統(tǒng)一的處理。
比如對(duì)某些敏感數(shù)據(jù)如用戶姓名、手機(jī)號(hào)等坐脫敏處理保存和查詢、對(duì)未實(shí)現(xiàn)權(quán)限的查詢通過添加關(guān)聯(lián)查詢實(shí)現(xiàn)權(quán)限控制查詢結(jié)果等等。
這時(shí),mybatis框架提供了攔截器的方式,允許在映射語(yǔ)句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用,進(jìn)行自己的業(yè)務(wù)處理。
1、使用方法
這里參考了官網(wǎng)的使用說明,只需實(shí)現(xiàn) Interceptor 接口,并在類中指定想要攔截的方法簽名即可。
比如:
@Intercepts({@Signature( ? type= Executor.class, ? method = "update", ? args = {MappedStatement.class,Object.class})}) public class ExamplePlugin implements Interceptor { ? private Properties properties = new Properties(); ? public Object intercept(Invocation invocation) throws Throwable { ? ? // implement pre processing if need ? ? Object returnObject = invocation.proceed(); ? ? // implement post processing if need ? ? return returnObject; ? } ? public void setProperties(Properties properties) { ? ? this.properties = properties; ? } }
然后在mybatis的配置文件中,添加插件的對(duì)應(yīng)配置即可。
<!-- mybatis-config.xml --> <plugins> ? <plugin interceptor="org.mybatis.example.ExamplePlugin"> ? ? <property name="someProperty" value="100"/> ? </plugin> </plugins>
我們也可以在代碼中添加,下面給出在spring中
//通過spring查找SqlSessionFactory對(duì)象的邏輯在此省略 SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) var3; org.apache.ibatis.session.Configuration c = sqlSessionFactory.getConfiguration(); c.addInterceptor(interceptor);
分別加上處理的業(yè)務(wù)邏輯,這個(gè)攔截器就可以使用了。
2、需要注意的地方
第一節(jié)簡(jiǎn)單介紹了,攔截器的使用方法,但在實(shí)際項(xiàng)目中這樣還遠(yuǎn)遠(yuǎn)不夠。
筆者在本節(jié)列舉了一些需要注意的地方,供大家思考討論。
攔截器的執(zhí)行順序
攔截器的調(diào)用順序分為兩大種,第一種是攔截的不同對(duì)象,第二種是指攔截同一種對(duì)象的同一個(gè)方法。
第一種情況,例如攔截 Executor 和 攔截 StatementHandler 就屬于不同的攔截對(duì)象, 這兩類的攔截器在整體執(zhí)行的邏輯上是不同的。
StatementHandler 屬于 Executor 執(zhí)行過程中的一個(gè)子過程。
所以這兩種不同類別的插件在配置時(shí),一定是先執(zhí)行 Executor 的攔截器,然后才會(huì)輪到 StatementHandler。
所以這種情況下配置攔截器的順序就不重要了,在 MyBatis 邏輯上就已經(jīng)控制了先后順序。
第二種情況,例如都攔截 Executor 的 query 方法,這時(shí)你配置攔截器的順序就會(huì)對(duì)這里有影響了。比如配置如下。
<plugins> ? ? <plugin interceptor="com.github.pagehelper.ExecutorQueryInterceptor1"/> ? ? <plugin interceptor="com.github.pagehelper.ExecutorQueryInterceptor2"/> ? ? <plugin interceptor="com.github.pagehelper.ExecutorQueryInterceptor3"/> </plugins>
前面我們配置攔截器的順序是1,2,3。在這里也會(huì)按照 1,2,3 的順序被層層代理,代理后的結(jié)構(gòu)如下:
Interceptor3:{ ? ? Interceptor2: { ? ? ? ? Interceptor1: { ? ? ? ? ? ? target: Executor ? ? ? ? } ? ? } }
然后到執(zhí)行的邏輯:
Interceptor3 前置處理
Interceptor2 前置處理
Interceptor1 前置處理
Object result = executor.query(4個(gè)參數(shù)方法);
Interceptor1 后續(xù)處理
Interceptor2 后續(xù)處理
Interceptor3 后續(xù)處理
return result;
順序就是 3>2>1>Executor>1>2>3。MyBatis的攔截器采用責(zé)任鏈設(shè)計(jì)模式,多個(gè)攔截器之間的責(zé)任鏈?zhǔn)峭ㄟ^動(dòng)態(tài)代理組織的。
我們一般都會(huì)在攔截器中的intercept方法中往往會(huì)有invocation.proceed()語(yǔ)句,其作用是將攔截器責(zé)任鏈向后傳遞,本質(zhì)上便是動(dòng)態(tài)代理的invoke。
與常用插件的整合遇到的問題
pageHelper造成分頁(yè)失效的問題
通過查看pagehelper源碼,可以看到其inercept方法直接獲取了excutor然后開始分頁(yè)查詢,當(dāng)查詢到結(jié)果時(shí),便返回了。
就是pagehelper的intercept方法中沒有invocation.proceed(),這意味著什么?
//com.github.pagehelper.PageInterceptor#intercept ..... ? ? ? ? ? ? ? ? resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); ? ? ? ? ? ? } ? ? ? ? ? ? return dialect.afterPage(resultList, parameter, rowBounds); ? ? ? ? } finally { ? ? ? ? ? ? dialect.afterAll(); ? ? ? ? }
這意味著pagehelper沒有繼續(xù)向后傳遞責(zé)任鏈,而是自行處理直接返回。
由此,我們可以猜出該問題大概率與攔截器的執(zhí)行順序有關(guān)。
通過斷點(diǎn)調(diào)試,驗(yàn)證了該猜想,當(dāng)遇到分頁(yè)查詢時(shí),執(zhí)行到pagehelper就結(jié)束了,沒有進(jìn)入我們的自定義攔截器。這就可能造成我們自定義攔截器失效。
解決方案
因?yàn)镻ageHelper是Excetor類型的攔截器,所以我們?nèi)绻胍赑ageHelper攔截器前面執(zhí)行,就必須要將我們自己的攔截器添加到他的攔截器后面。
這里只介紹最簡(jiǎn)單最優(yōu)雅的一種方式:
注冊(cè)一個(gè)ApplicationListener監(jiān)聽器,監(jiān)聽 ContextRefreshedEvent 事件,當(dāng)所有的bean都初始化完成后(即PageHelper也已經(jīng)注冊(cè)好了),再把我們的自定義 MyBatis 攔截器注冊(cè)到 SqlSessionFactory 中。
可以提升的點(diǎn)
Interceptor接口提供了三個(gè)方法分別是攔截器處理邏輯的主要方法、判斷是否要進(jìn)行攔截,然后做出決定是否生成一個(gè)代理的方法及設(shè)置參數(shù)的方法。
package org.apache.ibatis.plugin; import java.util.Properties; public interface Interceptor { ? Object intercept(Invocation invocation) throws Throwable; ? default Object plugin(Object target) { ? ? return Plugin.wrap(target, this); ? } ? default void setProperties(Properties properties) { ? ? // NOP ? } }
這里說的提升點(diǎn),就是在實(shí)現(xiàn)接口的實(shí)現(xiàn)類中,我們可以在plugin方法里加上一個(gè)判斷,因?yàn)槟J(rèn)情況下,攔截器根據(jù)順序攔截后,就可以去處理對(duì)應(yīng)邏輯了,這里加上一個(gè)判斷攔截的條件,可以減少代理類的創(chuàng)建。
? ? @Override ? ? public Object plugin(Object target) { ? ? ? ? if (target instanceof StatementHandler && checkIfNeeded((StatementHandler) target)) { ? ? ? ? ? ? return Plugin.wrap(target, this); ? ? ? ? } else { ? ? ? ? ? ? return target; ? ? ? ? } ? ? }
參考文檔:
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Mysql數(shù)據(jù)庫(kù)慢查詢常用優(yōu)化方式
數(shù)據(jù)庫(kù)SQL優(yōu)化是老生常談的問題,下面這篇文章主要給大家介紹了關(guān)于Mysql數(shù)據(jù)庫(kù)慢查詢常用優(yōu)化方式,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05兩大步驟教您開啟MySQL 數(shù)據(jù)庫(kù)遠(yuǎn)程登陸帳號(hào)的方法
在工作實(shí)踐和學(xué)習(xí)中,如何開啟 MySQL 數(shù)據(jù)庫(kù)的遠(yuǎn)程登陸帳號(hào)算是一個(gè)難點(diǎn)的問題,以下內(nèi)容便是在工作和實(shí)踐中總結(jié)出來(lái)的兩大步驟,能幫助DBA們順利的完成開啟 MySQL 數(shù)據(jù)庫(kù)的遠(yuǎn)程登陸帳號(hào)。2011-03-03MySQL表字段設(shè)置默認(rèn)值(圖文教程及注意細(xì)節(jié))
默認(rèn)值的設(shè)置很重要,比如在插入的時(shí)候一些字段是可以省略的,這會(huì)帶來(lái)很多的方便,接下來(lái)將要介紹MySQL表字段設(shè)置默認(rèn)值感興趣的你可以千萬(wàn)不要走開啊,希望本文對(duì)你有所幫助2013-01-01mySQL占用虛擬內(nèi)存達(dá)8百多兆問題解決思路
為了裝mysql環(huán)境測(cè)試,裝上后發(fā)現(xiàn)啟動(dòng)后mysql占用了很大的虛擬內(nèi)存,達(dá)8百多兆,需要的朋友可以參考下2012-12-12MySQL動(dòng)態(tài)創(chuàng)建表,數(shù)據(jù)分表的存儲(chǔ)過程
MySQL動(dòng)態(tài)創(chuàng)建表,數(shù)據(jù)分表的存儲(chǔ)過程,需要的朋友可以參考下。2011-08-08