美文网首页
PageHelper导致自定义Mybatis拦截器不生效

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

作者: water_lang | 来源:发表于2019-09-25 18:27 被阅读0次

    背景:

    最近由于公司要做统一的数据变更记录,以前是基于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

    相关文章

      网友评论

          本文标题:PageHelper导致自定义Mybatis拦截器不生效

          本文链接:https://www.haomeiwen.com/subject/ggomyctx.html