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

JAVA小咖秀

共 4758字,需浏览 10分钟

 ·

2021-04-19 15:00


来源:www.jianshu.com/p/8dc9f8a4cce9

背景:

最近由于公司要做统一的数据变更记录,以前是基于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拦截器的不生效的问题,搞完了。

最近给大家找了  JVM学习视频


资源,怎么领取?


扫二维码为,加我微信,回复:JVM

 注意,不要乱回复 

没错,不是机器人
记得一定要等待,等待才有好东西
浏览 37
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报