PageHelper導(dǎo)致自定義Mybatis攔截器不生效
來源:www.jianshu.com/p/8dc9f8a4cce9
背景:
最近由于公司要做統(tǒng)一的數(shù)據(jù)變更記錄,以前是基于Aop來做的,這樣效率很低,而且在做批量處理(insert,update,delete)操作時基本不可用。所以我打算使用CDC(如Canal,Maxwell等工具)來監(jiān)聽mysql的binlog來做。
但是不是所有的表都會有user_id字段,所以我們須要在sql上做一些處理,因為公司現(xiàn)在統(tǒng)一用的是mybatis,那么現(xiàn)在我覺得比較好的方式就是在mybatis上進(jìn)行攔截改造sql.將userId從應(yīng)用層獲取到并寫入到須要執(zhí)行的sql上(只對insert,update,delete記錄)。
如:有如下sql:
update table set a= 1 where name =3
改造的結(jié)果就是:
/** userId:1,traceId:123456**/
update table set a= 1 where name =3
這樣我們就可以記錄一次操作改了哪些數(shù)據(jù),改數(shù)據(jù)的人是哪個。
開始干:
這里面有幾個技術(shù)點,且都不怎么復(fù)雜,今天我們只聊mybatis攔截器。其實寫一個攔截器還是很簡單的,網(wǎng)上有很多的代碼。代碼寫完后,突然發(fā)現(xiàn)有些項目的自定義mybatis攔截器沒有生效。
于是就開始google研究了一下,發(fā)現(xiàn)是因為我們這些不生效的項目使用了PageHelper.于是找了一些大神的解決方案,和攔截器的順序有關(guān)。先說一下結(jié)論:
MyBatis的攔截器采用責(zé)任鏈設(shè)計模式,多個攔截器之間的責(zé)任鏈?zhǔn)峭ㄟ^動態(tài)代理組織的。我們一般都會在攔截器中的intercept方法中往往會有invocation.proceed()語句,其作用是將攔截器責(zé)任鏈向后傳遞,本質(zhì)上便是動態(tài)代理的invoke。
PageHelper在intercept方法中執(zhí)行完后沒有執(zhí)行invocation.proceed(),意味著這玩意兒沒有繼續(xù)傳遞責(zé)任鏈(可能他有自己的想法)。所以他就沒有進(jìn)入我們自己的攔截器。
注意,敲黑板:
A.不是所有的攔截器都必須要指定先后順序。
攔截器的調(diào)用順序分為兩大種,第一種是攔截的不同對象,例如攔截 Executor 和 攔截 StatementHandler 就屬于不同的攔截對象, 這兩類的攔截器在整體執(zhí)行的邏輯上是不同的,在 Executor 中的 query 方法執(zhí)行過程中會調(diào)用StatementHandler。
所以StatementHandler 屬于 Executor 執(zhí)行過程中的一個子過程。所以這兩種不同類別的插件在配置時,一定是先執(zhí)行 Executor 的攔截器,然后才會輪到 StatementHandler。所以這種情況下配置攔截器的順序就不重要了,在 MyBatis 邏輯上就已經(jīng)控制了先后順序。
所以如果你一個是Executor 類型的攔截器,一個是StatementHandler類型的攔截器,你可以不用管他順序,也就是說你只須要定義好類型都Executor的攔截器順序。
B.類型都為Executor的攔截器順序問題。
如果你的攔截器定義的順序是這樣的(你可以通過獲取sqlSessionFactory.getConfiguration()去查看里面的InterceptorChain然后看到各個interceptor的順序):
<plugins>
<plugin interceptor="com.github.pagehelper.ExecutorQueryInterceptor1"/>
<plugin interceptor="com.github.pagehelper.ExecutorQueryInterceptor2"/>
<plugin interceptor="com.github.pagehelper.ExecutorQueryInterceptor3"/>
</plugins>
他執(zhí)行的順序不是先執(zhí)行1,2,3,而執(zhí)行的順序是3,2,1。
Interceptor3:{
Interceptor2: {
Interceptor1: {
target: Executor
}
}
}
從這個結(jié)構(gòu)應(yīng)該就很容易能看出來,將來執(zhí)行的時候肯定是按照 3>2>1>Executor>1>2>3 的順序去執(zhí)行的。可能有些人不知道為什么3>2>1>Executor之后會有1>2>3,這是因為使用代理時,調(diào)用完代理方法后,還能繼續(xù)進(jìn)行其他處理。處理結(jié)束后,將代理方法的返回值繼續(xù)往外返回即可。
C.解決方案
因為PageHelper是Excetor類型的攔截器,所以按照前面兩條的理論,我們?nèi)绻胍赑ageHelper攔截器前面執(zhí)行,就必須要將我們自己的攔截器添加到他的攔截器后面。
那該怎么做呢?
我們可以通過這種方式來做:
我們?nèi)タ碢ageHelperAutoConfiguration的代碼是不是發(fā)現(xiàn),該類上面有一個@AutoConfigureAfter(MybatisAutoConfiguration.class)注解,這表明他是在MybatisAutoConfiguration加載完成后,才執(zhí)行自己的加載。那么我們是不是可以也可以構(gòu)建一個類似的代碼呢,雖然我們不是一個starter,但是我們可以通過這種操作來實現(xiàn)我們的須求。
1.在src/main/resources/META-INF目錄下面,創(chuàng)建一個spring.factories的文件
2.spring.factories里的內(nèi)容是:org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.llyt.exculd.TestLogAutoConfiguration這個com.llyt.exculd.TestLogAutoConfiguration,就是你自己的配置類的全路徑。該類的代碼在后面。
3.TestLogAutoConfiguration代碼:
@Configuration
@AutoConfigureAfter(PageHelperAutoConfiguration.class)
public class TestLogAutoConfiguration {
@Autowired
private List<SqlSessionFactory> sqlSessionFactoryList;
@PostConstruct
public void addMyInterceptor() {
ExampleOnePlugin e = new ExampleOnePlugin();
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
sqlSessionFactory.getConfiguration().addInterceptor(e);
}
}
}
至此,這種方法就OK了。但是你可能會執(zhí)行不成功,該類的addMyInterceptor方法總是先于PageHelperAutoConfiguration的addPageInterceptor()方法執(zhí)行,這就意味著你的攔截器總是添加到在pageHelper攔截前面的,那么他總是在PageHelper攔截器后面執(zhí)行。
如果出現(xiàn)這種情況,說明你可能在spring boot主類上配置了
@ComponentScan("****"),且該類會被這個掃描到,這個就是導(dǎo)致的原因所在。
這里面有一個知識點就是,不是配置了@AutoConfigureAfter(A.class)就一定表示該類一定在A類后面執(zhí)行。
如果配置類在 spring.factories 中配置了且而如果你的類被自己 Spring Boot 啟動類掃描到了,那么該類會被會優(yōu)先掃描到,配置類對順序有要求時就會出錯。那么該怎么解決呢?
解決的方法有兩個:
a.使用騷操作。
如果你將自己的配置類放到特別的包下,不使用 Spring Boot 啟動類掃描。完全通過 spring.factories 讀取配置就可以實現(xiàn)這個目的。
比如,你@ComponentScan掃描的包是com.bb.cc,那么你就將該配置類放在com.bb.dd包下面。
b.如果你覺得上面這種不習(xí)慣,可以用使用excludeFilters。
@ComponentScan(basePackages = {"com.llyt"}, excludeFilters = @ComponentScan.Filter(
type = FilterType.REGEX,
pattern = "com.llyt.exculd.*"))
將你的配置類放在com.llyt.exculd包下面就行了。
至此,mybatis攔截器的不生效的問題,搞完了。
最近給大家找了 JVM學(xué)習(xí)視頻
資源,怎么領(lǐng)取?
掃二維碼為,加我微信,回復(fù):JVM
注意,不要亂回復(fù) 沒錯,不是機(jī)器人 記得一定要等待,等待才有好東西
