PageHelper导致自定义Mybatis拦截器不生效

背景:

最近由于公司要做统一的数据变更记录,以前是基于Aop来做的,这样效率很低,而且在做批量处理(insert,update,delete)操作时基本不可用。所以我打算使用CDC(如Canal,Maxwell等工具)来监听mysql的binlog来做。但是不是所有的表都会有user_id字段,所以我们须要在sql上做一些处理,因为公司现在统一用的是mybatis,那么现在我觉得比较好的方式就是在mybatis上进行拦截改造sql.将userId从应用层获取到并写入到须要执行的sql上(只对insert,update,delete记录)。
如:有如下sql: update table set a= 1 where name =3
改造的结果就是:/** userId:1,traceId:123456**/ update table set a= 1 where name =3
这样我们就可以记录一次操作改了哪些数据,改数据的人是哪个。

开始干:

这里面有几个技术点,且都不怎么复杂,今天我们只聊mybatis拦截器。其实写一个拦截器还是很简单的,网上有很多的代码。代码写完后,突然发现有些项目的自定义mybatis拦截器没有生效。于是就开始google研究了一下,发现是因为我们这些不生效的项目使用了PageHelper.于是找了一些大神的解决方案,和拦截器的顺序有关。先说一下结论:
MyBatis的拦截器采用责任链设计模式,多个拦截器之间的责任链是通过动态代理组织的。我们一般都会在拦截器中的intercept方法中往往会有invocation.proceed()语句,其作用是将拦截器责任链向后传递,本质上便是动态代理的invoke

PageHelper在intercept方法中执行完后没有执行invocation.proceed(),意味着这玩意儿没有继续传递责任链(可能他有自己的想法)。所以他就没有进入我们自己的拦截器。

注意,敲黑板:

A.不是所有的拦截器都必须要指定先后顺序。
拦截器的调用顺序分为两大种,第一种是拦截的不同对象,例如拦截 Executor 和 拦截 StatementHandler 就属于不同的拦截对象, 这两类的拦截器在整体执行的逻辑上是不同的,在 Executor 中的 query 方法执行过程中会调用StatementHandler。

所以StatementHandler 属于 Executor 执行过程中的一个子过程。 所以这两种不同类别的插件在配置时,一定是先执行 Executor 的拦截器,然后才会轮到 StatementHandler。所以这种情况下配置拦截器的顺序就不重要了,在 MyBatis 逻辑上就已经控制了先后顺序。
所以如果你一个是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>
他执行的顺序不是先执行1,2,3,而执行的顺序是3,2,1。

Interceptor3:{
    Interceptor2: {
        Interceptor1: {
            target: Executor
        }
    }
}

从这个结构应该就很容易能看出来,将来执行的时候肯定是按照 3>2>1>Executor>1>2>3 的顺序去执行的。 可能有些人不知道为什么3>2>1>Executor之后会有1>2>3,这是因为使用代理时,调用完代理方法后,还能继续进行其他处理。处理结束后,将代理方法的返回值继续往外返回即可。

C(解决方案).因为PageHelper是Excetor类型的拦截器,所以按照前面两条的理论,我们如果想要在PageHelper拦截器前面执行,就必须要将我们自己的拦截器添加到他的拦截器后面。

那该怎么做呢?
我们可以通过这种方式来做:

我们去看PageHelperAutoConfiguration的代码是不是发现,该类上面有一个@AutoConfigureAfter(MybatisAutoConfiguration.class)注解,这表明他是在MybatisAutoConfiguration加载完成后,才执行自己的加载。那么我们是不是可以也可以构建一个类似的代码呢,虽然我们不是一个starter,但是我们可以通过这种操作来实现我们的须求。

(1)在src/main/resources/META-INF目录下面,创建一个spring.factories的文件
(2)spring.factories里的内容是:
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了。但是你可能会执行不成功,该类的addMyInterceptor方法总是先于PageHelperAutoConfiguration的addPageInterceptor()方法执行,这就意味着你的拦截器总是添加到在pageHelper拦截前面的,那么他总是在PageHelper拦截器后面执行。
如果出现这种情况,说明你可能在spring boot主类上配置了
@ComponentScan("****"),且该类会被这个扫描到,这个就是导致的原因所在。

这里面有一个知识点就是,不是配置了@AutoConfigureAfter(A.class)就一定表示该类一定在A类后面执行。

如果配置类在 spring.factories 中配置了且而如果你的类被自己 Spring Boot 启动类扫描到了,那么该类会被会优先扫描到,配置类对顺序有要求时就会出错。

那么该怎么解决呢?

解决的方法有两个:

a.使用骚操作。

如果你将自己的配置类放到特别的包下,不使用 Spring Boot 启动类扫描。完全通过 spring.factories 读取配置就可以实现这个目的。
比如,你@ComponentScan扫描的包是com.bb.cc,那么你就将该配置类放在com.bb.dd包下面。

b.如果你觉得上面这种不习惯,可以用使用excludeFilters :
@ComponentScan(basePackages = {"com.llyt"},  excludeFilters = @ComponentScan.Filter(
     type = FilterType.REGEX,
     pattern = "com.llyt.exculd.*"))

将你的配置类放在com.llyt.exculd包下面就行了。

至此,mybatis拦截器的不生效的问题,搞完了。

参考文献:
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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 1.It's time for breakfast. 到吃早餐的时间了。 2.Comeand eat your b...
    秀火锅阅读 85评论 0 0
  • 今年下半年,接到了新一个项目——劳动关系协调员考证班,但前期一直在忙单位的事,还有其他项目,没有更多时间投入,以为...
    无忧侠阅读 239评论 0 1
  • 雾柳如烟,清荷倩影映湖天。每为看花心意笃,幽波渌,并蒂真情莲不俗。 ​
    郭大牛阅读 565评论 6 16
  • 身为一名乡村教师,以前我总想着如何在有限的条件下带给孩子们更多专业而多姿的课程,让孩子们的见识走出大山,领略世界的...
    梧州市三贵小学李玉姬阅读 354评论 0 0