背景:
最近由于公司要做統(tǒng)一的數(shù)據(jù)變更記錄庇勃,以前是基于Aop來做的檬嘀,這樣效率很低,而且在做批量處理(insert,update,delete)操作時(shí)基本不可用责嚷。所以我打算使用CDC(如Canal,Maxwell等工具)來監(jiān)聽mysql的binlog來做鸳兽。但是不是所有的表都會(huì)有user_id字段,所以我們須要在sql上做一些處理罕拂,因?yàn)楣粳F(xiàn)在統(tǒng)一用的是mybatis揍异,那么現(xiàn)在我覺得比較好的方式就是在mybatis上進(jìn)行攔截改造sql.將userId從應(yīng)用層獲取到并寫入到須要執(zhí)行的sql上(只對(duì)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ù)的人是哪個(gè)衷掷。
開始干:
這里面有幾個(gè)技術(shù)點(diǎn)辱姨,且都不怎么復(fù)雜,今天我們只聊mybatis攔截器戚嗅。其實(shí)寫一個(gè)攔截器還是很簡單的雨涛,網(wǎng)上有很多的代碼。代碼寫完后渡处,突然發(fā)現(xiàn)有些項(xiàng)目的自定義mybatis攔截器沒有生效镜悉。于是就開始google研究了一下,發(fā)現(xiàn)是因?yàn)槲覀冞@些不生效的項(xiàng)目使用了PageHelper.于是找了一些大神的解決方案医瘫,和攔截器的順序有關(guān)侣肄。先說一下結(jié)論:
MyBatis的攔截器采用責(zé)任鏈設(shè)計(jì)模式,多個(gè)攔截器之間的責(zé)任鏈?zhǔn)峭ㄟ^動(dòng)態(tài)代理組織的醇份。我們一般都會(huì)在攔截器中的intercept方法中往往會(huì)有invocation.proceed()語句稼锅,其作用是將攔截器責(zé)任鏈向后傳遞,本質(zhì)上便是動(dòng)態(tài)代理的invoke僚纷。
PageHelper在intercept方法中執(zhí)行完后沒有執(zhí)行invocation.proceed()矩距,意味著這玩意兒沒有繼續(xù)傳遞責(zé)任鏈(可能他有自己的想法)。所以他就沒有進(jìn)入我們自己的攔截器怖竭。
注意锥债,敲黑板:
A.不是所有的攔截器都必須要指定先后順序。
攔截器的調(diào)用順序分為兩大種痊臭,第一種是攔截的不同對(duì)象哮肚,例如攔截 Executor 和 攔截 StatementHandler 就屬于不同的攔截對(duì)象, 這兩類的攔截器在整體執(zhí)行的邏輯上是不同的广匙,在 Executor 中的 query 方法執(zhí)行過程中會(huì)調(diào)用StatementHandler允趟。
所以StatementHandler 屬于 Executor 執(zhí)行過程中的一個(gè)子過程。 所以這兩種不同類別的插件在配置時(shí)鸦致,一定是先執(zhí)行 Executor 的攔截器潮剪,然后才會(huì)輪到 StatementHandler。所以這種情況下配置攔截器的順序就不重要了分唾,在 MyBatis 邏輯上就已經(jīng)控制了先后順序抗碰。
所以如果你一個(gè)是Executor 類型的攔截器,一個(gè)是StatementHandler類型的攔截器绽乔,你可以不用管他順序改含,也就是說你只須要定義好類型都Executor的攔截器順序。
B.類型都為Executor的攔截器順序問題:
如果你的攔截器定義的順序是這樣的(你可以通過獲取sqlSessionFactory.getConfiguration()去查看里面的InterceptorChain然后看到各個(gè)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
}
}
}
從這個(gè)結(jié)構(gòu)應(yīng)該就很容易能看出來骤视,將來執(zhí)行的時(shí)候肯定是按照 3>2>1>Executor>1>2>3 的順序去執(zhí)行的。 可能有些人不知道為什么3>2>1>Executor之后會(huì)有1>2>3鹃觉,這是因?yàn)槭褂么頃r(shí)专酗,調(diào)用完代理方法后,還能繼續(xù)進(jìn)行其他處理盗扇。處理結(jié)束后祷肯,將代理方法的返回值繼續(xù)往外返回即可。
C(解決方案).因?yàn)镻ageHelper是Excetor類型的攔截器疗隶,所以按照前面兩條的理論佑笋,我們?nèi)绻胍赑ageHelper攔截器前面執(zhí)行,就必須要將我們自己的攔截器添加到他的攔截器后面斑鼻。
那該怎么做呢蒋纬?
我們可以通過這種方式來做:
我們?nèi)タ碢ageHelperAutoConfiguration的代碼是不是發(fā)現(xiàn),該類上面有一個(gè)@AutoConfigureAfter(MybatisAutoConfiguration.class)注解坚弱,這表明他是在MybatisAutoConfiguration加載完成后蜀备,才執(zhí)行自己的加載。那么我們是不是可以也可以構(gòu)建一個(gè)類似的代碼呢荒叶,雖然我們不是一個(gè)starter碾阁,但是我們可以通過這種操作來實(shí)現(xiàn)我們的須求。
(1)在src/main/resources/META-INF目錄下面些楣,創(chuàng)建一個(gè)spring.factories的文件
(2)spring.factories里的內(nèi)容是:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.llyt.exculd.TestLogAutoConfiguration
這個(gè)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了。但是你可能會(huì)執(zhí)行不成功埋市,該類的addMyInterceptor方法總是先于PageHelperAutoConfiguration的addPageInterceptor()方法執(zhí)行冠桃,這就意味著你的攔截器總是添加到在pageHelper攔截前面的命贴,那么他總是在PageHelper攔截器后面執(zhí)行道宅。
如果出現(xiàn)這種情況,說明你可能在spring boot主類上配置了
@ComponentScan("****")胸蛛,且該類會(huì)被這個(gè)掃描到污茵,這個(gè)就是導(dǎo)致的原因所在。
這里面有一個(gè)知識(shí)點(diǎn)就是葬项,不是配置了@AutoConfigureAfter(A.class)就一定表示該類一定在A類后面執(zhí)行泞当。
如果配置類在 spring.factories 中配置了且而如果你的類被自己 Spring Boot 啟動(dòng)類掃描到了,那么該類會(huì)被會(huì)優(yōu)先掃描到民珍,配置類對(duì)順序有要求時(shí)就會(huì)出錯(cuò)襟士。
那么該怎么解決呢盗飒?
解決的方法有兩個(gè):
a.使用騷操作。
如果你將自己的配置類放到特別的包下陋桂,不使用 Spring Boot 啟動(dòng)類掃描逆趣。完全通過 spring.factories 讀取配置就可以實(shí)現(xiàn)這個(gè)目的。
比如嗜历,你@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攔截器的不生效的問題,搞完了暴匠。
參考文獻(xiàn):
http://xtong.tech/2018/08/01/MyBatis%E6%8B%A6%E6%88%AA%E5%99%A8%E5%9B%A0pagehelper%E8%80%8C%E5%A4%B1%E6%95%88%E7%9A%84%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/
https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/Interceptor.md