在springboot中如何给mybatis加拦截器

目录
  • 1、实现Interceptor接口,并添加拦截注解 @Intercepts
    • 1.在mybatis中可被拦截的类型有四种(按照拦截顺序)
    • 2.各个参数的含义
  • 2、在配置文件中添加拦截器
    • (1)第一种
    • (2)第二种
    • (3)第三种

拦截器的作用就是我们可以拦截某些方法的调用,在目标方法前后加上我们自己逻辑

Mybatis拦截器设计的一个初衷是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。

mybatis 自定义拦截器

1、实现Interceptor 接口,并添加拦截注解 @Intercepts

2、配置文件中添加拦截器

1、实现Interceptor接口,并添加拦截注解 @Intercepts

mybatis 拦截器默认可拦截的类型只有四种

  • Executor:拦截执行器的方法。
  • ParameterHandler:拦截参数的处理。
  • ResultHandler:拦截结果集的处理。
  • StatementHandler:拦截Sql语法构建的处理。

对于我们的自定义拦截器必须使用 mybatis 提供的注解来指明我们要拦截的是四类中的哪一个类接口

具体规则如下:

a:Intercepts 拦截器:标识我的类是一个拦截器

b:Signature 署名:则是指明我们的拦截器需要拦截哪一个接口的哪一个方法

  • type 对应四类接口中的某一个,比如是 Executor
  • method 对应接口中的哪类方法,比如 Executor 的 update 方法
  • args 对应接口中的哪一个方法,比如 Executor 中 query 因为重载原因,方法有多个,args 就是指明参数类型,从而确定是哪一个方法
@Intercepts({
        @Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class}),
        @Signature(method = "query", type = StatementHandler.class, args = {Statement.class, ResultHandler.class})
})
public class MyInterceptor implements Interceptor {
    /**
     * 这个方法很好理解
     * 作用只有一个:我们不是拦截方法吗,拦截之后我们要做什么事情呢?
     *      这个方法里面就是我们要做的事情
     *
     * 解释这个方法前,我们一定要理解方法参数 {@link Invocation} 是个什么鬼?
     * 1 我们知道,mybatis拦截器默认只能拦截四种类型 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler
     * 2 不管是哪种代理,代理的目标对象就是我们要拦截对象,举例说明:
     *      比如我们要拦截 {@link Executor#update(MappedStatement ms, Object parameter)} 方法,
     *      那么 Invocation 就是这个对象,Invocation 里面有三个参数 target method args
     *          target 就是 Executor
     *          method 就是 update
     *          args   就是 MappedStatement ms, Object parameter
     *
     *   如果还是不能理解,我再举一个需求案例:看下面方法代码里面的需求
     *
     *  该方法在运行时调用
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        /*
         * 需求:我们需要对所有更新操作前打印查询语句的 sql 日志
         * 那我就可以让我们的自定义拦截器 MyInterceptor 拦截 Executor 的 update 方法,在 update 执行前打印sql日志
         * 比如我们拦截点是 Executor 的 update 方法 :  int update(MappedStatement ms, Object parameter)
         *
         * 那当我们日志打印成功之后,我们是不是还需要调用这个query方法呢,如何如调用呢?
         * 所以就出现了 Invocation 对象,它这个时候其实就是一个 Executor,而且 method 对应的就是 query 方法,我们
         * 想要调用这个方法,只需要执行 invocation.proceed()
         */
        /* 因为我拦截的就是Executor,所以我可以强转为 Executor,默认情况下,这个Executor 是个 SimpleExecutor */
        Executor executor = (Executor)invocation.getTarget();
        /*
         * Executor 的 update 方法里面有一个参数 MappedStatement,它是包含了 sql 语句的,所以我获取这个对象
         * 以下是伪代码,思路:
         * 1 通过反射从 Executor 对象中获取 MappedStatement 对象
         * 2 从 MappedStatement 对象中获取 SqlSource 对象
         * 3 然后从 SqlSource 对象中获取获取 BoundSql 对象
         * 4 最后通过 BoundSql#getSql 方法获取 sql
         */
        MappedStatement mappedStatement = ReflectUtil.getMethodField(executor, MappedStatement.class);
        SqlSource sqlSource = ReflectUtil.getField(mappedStatement, SqlSource.class);
        BoundSql boundSql = sqlSource.getBoundSql(args);
        String sql = boundSql.getSql();
        logger.info(sql);
        /*
         * 现在日志已经打印,需要调用目标对象的方法完成 update 操作
         * 我们直接调用 invocation.proceed() 方法
         * 进入源码其实就是一个常见的反射调用 method.invoke(target, args)
         * target 对应 Executor对象
         * method 对应 Executor的update方法
         * args   对应 Executor的update方法的参数
         */
        return invocation.proceed();
    }
    /**
     * 这个方法也很好理解
     * 作用就只有一个:那就是Mybatis在创建拦截器代理时候会判断一次,当前这个类 MyInterceptor 到底需不需要生成一个代理进行拦截,
     * 如果需要拦截,就生成一个代理对象,这个代理就是一个 {@link Plugin},它实现了jdk的动态代理接口 {@link InvocationHandler},
     * 如果不需要代理,则直接返回目标对象本身
     *
     * Mybatis为什么会判断一次是否需要代理呢?
     * 默认情况下,Mybatis只能拦截四种类型的接口:Executor、StatementHandler、ParameterHandler 和 ResultSetHandler
     * 通过 {@link Intercepts} 和 {@link Signature} 两个注解共同完成
     * 试想一下,如果我们开发人员在自定义拦截器上没有指明类型,或者随便写一个拦截点,比如Object,那Mybatis疯了,难道所有对象都去拦截
     * 所以Mybatis会做一次判断,拦截点看看是不是这四个接口里面的方法,不是则不拦截,直接返回目标对象,如果是则需要生成一个代理
     *
     *  该方法在 mybatis 加载核心配置文件时被调用
     */
    @Override
    public Object plugin(Object target) {
        /*
         * 看了这个方法注释,就应该理解,这里的逻辑只有一个,就是让mybatis判断,要不要进行拦截,然后做出决定是否生成一个代理
         *
         * 下面代码什么鬼,就这一句就搞定了?
         * Mybatis判断依据是利用反射,获取这个拦截器 MyInterceptor 的注解 Intercepts和Signature,然后解析里面的值,
         * 1 先是判断要拦截的对象是四个类型中 Executor、StatementHandler、ParameterHandler、 ResultSetHandler 的哪一个
         * 2 然后根据方法名称和参数(因为有重载)判断对哪一个方法进行拦截  Note:mybatis可以拦截这四个接口里面的任一一个方法
         * 3 做出决定,是返回一个对象呢还是返回目标对象本身(目标对象本身就是四个接口的实现类,我们拦截的就是这四个类型)
         *
         * 好了,理解逻辑我们写代码吧~~~  What !!! 要使用反射,然后解析注解,然后根据参数类型,最后还要生成一个代理对象
         * 我一个小白我怎么会这么高大上的代码嘛,怎么办?
         *
         * 那就是使用下面这句代码吧  哈哈
         * mybatis 早就考虑了这里的复杂度,所以提供这个静态方法来实现上面的逻辑
         */
        return Plugin.wrap(target, this);
    }
    /**
     * 这个方法最好理解,如果我们拦截器需要用到一些变量参数,而且这个参数是支持可配置的,
     *  类似Spring中的@Value("${}")从application.properties文件获取
     * 这个时候我们就可以使用这个方法
     *
     * 如何使用?
     * 只需要在 mybatis 配置文件中加入类似如下配置,然后 {@link Interceptor#setProperties(Properties)} 就可以获取参数
     *      <plugin interceptor="liu.york.mybatis.study.plugin.MyInterceptor">
     *           <property name="username" value="LiuYork"/>
     *           <property name="password" value="123456"/>
     *      </plugin>
     *      方法中获取参数:properties.getProperty("username");
     *
     * 问题:为什么要存在这个方法呢,比如直接使用 @Value("${}") 获取不就得了?
     * 原因是 mybatis 框架本身就是一个可以独立使用的框架,没有像 Spring 这种做了很多依赖注入的功能
     *
     *  该方法在 mybatis 加载核心配置文件时被调用
     */
    @Override
    public void setProperties(Properties properties) {
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");
        // TODO: 2019/2/28  业务逻辑处理...
    }
}

三个核心方法都加了详细的注释,而且结合案例需求说明问题

那么多文字不想行看,没关系有概括

总结:

1.在mybatis中可被拦截的类型有四种(按照拦截顺序)

  • Executor:拦截执行器的方法。
  • ParameterHandler:拦截参数的处理。
  • ResultHandler:拦截结果集的处理。
  • StatementHandler:拦截Sql语法构建的处理。

2.各个参数的含义

  • @Intercepts:标识该类是一个拦截器;
  • @Signature:指明自定义拦截器需要拦截哪一个类型,哪一个方法;

2.1 type:对应四种类型中的一种;

2.2 method:对应接口中的哪个方法;

2.3 args:对应哪一个方法参数类型(因为可能存在重载方法);

接下来我们看看 Plugin 类

package org.apache.ibatis.plugin;
/**
 * Plugin 类其实就是一个代理类,因为它实现了jdk动态代理接口 InvocationHandler
 * 我们核心只需要关注两个方法
 * wrap:
 *      如果看懂了代码案例1的例子,那么这个方法很理解,这个方法就是 mybatis 提供给开发人员使用的一个工具类方法,
 *      目的就是帮助开发人员省略掉 反射解析注解 Intercepts 和 Signature,有兴趣的可以去看看源码 Plugin#getSignatureMap 方法
 *
 * invoke:
 *      这个方法就是根据 wrap 方法的解析结果,判断当前拦截器是否需要进行拦截,
 *      如果需要拦截:将 目标对象+目标方法+目标参数 封装成一个 Invocation 对象,给我们自定义的拦截器 MyInterceptor 的 intercept 方法
 *                   这个时候就刚好对应上了上面案例1中对 intercept 方法的解释了,它就是我们要处理自己逻辑的方法,
 *                   处理好了之后是否需要调用目标对象的方法,比如上面说的 打印了sql语句,是否还要查询数据库呢?答案是肯定的
 *      如果不需要拦截:则直接调用目标对象的方法
 *                   比如直接调用 Executor 的 update 方法进行更新数据库
 *
 */
class Plugin implements InvocationHandler {
    public static Object wrap(Object target, Interceptor interceptor) {
        // 省略
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 省略
    }
}

贴一段网上的通用解释吧:

  • Plugin的wrap方法,它根据当前的Interceptor上面的注解定义哪些接口需要拦截,然后判断当前目标对象是否有实现对应需要拦截的接口,如果没有则返回目标对象本身,如果有则返回一个代理对象。
  • 而这个代理对象的InvocationHandler正是一个Plugin。所以当目标对象在执行接口方法时,如果是通过代理对象执行的,则会调用对应InvocationHandler的invoke方法,也就是Plugin的invoke方法。
  • 所以接着我们来看一下该invoke方法的内容。
  • 这里invoke方法的逻辑是:如果当前执行的方法是定义好的需要拦截的方法,则把目标对象、要执行的方法以及方法参数封装成一个Invocation对象,再把封装好的Invocation作为参数传递给当前拦截器的intercept方法。
  • 如果不需要拦截,则直接调用当前的方法。
  • Invocation中定义了定义了一个proceed方法,其逻辑就是调用当前方法,所以如果在intercept中需要继续调用当前方法的话可以调用invocation的procced方法。

这就是Mybatis中实现Interceptor拦截的一个思想

2、在配置文件中添加拦截器

在springboot中要给mybatis加上这个拦截器,有三种方法,前两种方法在启动项目时不会自动调用自定义拦截器的setProperties方法。

拦截器顺序

1、不同拦截器顺序

Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler

2、对于同一个类型的拦截器的不同对象拦截顺序:

在 mybatis 核心配置文件根据配置的位置,拦截顺序是 从上往下

(1)第一种

直接给自定义拦截器添加一个 @Component注解,当调用sql时结果如下,可以看到拦截器生效了,但是启动时候并没有自动调用setProperties方法。

@Component
@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
public class MybatisInterceptor implements Interceptor {
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		//业务代码
	}
	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}
	@Override
	public void setProperties(Properties properties) {
		// TODO Auto-generated method stub
	}
}

(2)第二种

在配置类里添加拦截器,这种方法结果同上,也不会自动调用setProperties方法。

@Configuration
public class MybatisConfig {
    @Bean
    public ConfigurationCustomizer mybatisConfigurationCustomizer() {
        return new ConfigurationCustomizer() {
            @Override
            public void customize(Configuration configuration) {
                configuration.addInterceptor(new MybatisInterceptor());
            }
        };
    }
}

(3)第三种

这种方法就是跟以前的配置方法类似,在yml配置文件中指定mybatis的xml配置文件,

注意:config-location属性和configuration属性不能同时指定

mybatis:
  config-location: classpath:mybatis.xml
  type-aliases-package: me.zingon.pagehelper.model
  mapper-locations: classpath:mapper/*.xml

mybatis.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <package name="me.zingon.pacargle.model"/>
    </typeAliases>
    <plugins>
        <plugin interceptor="me.zingon.pagehelper.interceptor.MyPageInterceptor">
            <property name="dialect" value="oracle"/>
        </plugin>
    </plugins>
</configuration>

可以看到,在启动项目的时候setProperties被自动调用了

总结:前两种方法可以在初始化自定义拦截器的时候通过 @Value 注解直接初始化需要的参数。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • springboot+mybatis-plus基于拦截器实现分表的示例代码

    目录 前言 一.设计思路 二.实现思路 三.代码实现 接口描述 核心组成部分 1.本地线程工具类 2.注解部分 3.拦截器实现 四.测试 后记 前言 最近在工作遇到数据量比较多的情况,单表压力比较大,crud的操作都受到影响,因为某些原因,项目上没有引入sharding-jdbc这款优秀的分表分库组件,所以打算简单写一个基于mybatis拦截器的分表实现 一.设计思路 在现有的业务场景下,主要实现的目标就是表名的替换,需要解决的问题有 如何从执行的方法中,获取对应的sql并解析获取当前执行的表名

  • springboot整合mybatis分页拦截器的问题小结

    简介 又到了吹水时间,是这样的,今天开发时想将自己写好的代码拿来优化,因为不想在开发服弄,怕搞坏了到时候GIT到生产服一大堆问题,然后把它分离到我轮子(工具)项目上,最后运行后发现我获取List的时候很卡至少10秒,我惊了平时也就我的正常版本是800ms左右(不要看它很久,因为数据量很大,也很正常.),前提是我也知道很慢,就等的确需要优化时,我在放出我优化的plus版本,回到10秒哪里,最开始我刚刚接到这个app项目时,在我用 PageHelper.startPage(page, num);(分

  • springboot+mybatis拦截器方法实现水平分表操作

    目录 1.前言 2.MyBatis 允许使用插件来拦截的方法 3.Interceptor接口 4分表实现 4.1.大体思路 4.2.1 Mybatis如何找到我们新增的拦截服务 4.2.2 应该拦截什么样的对象 4.2.3 实现自定义拦截器 4.2.逐步实现 1.前言 业务飞速发展导致了数据规模的急速膨胀,单机数据库已经无法适应互联网业务的发展.由于MySQL采用 B+树索引,数据量超过阈值时,索引深度的增加也将使得磁盘访问的 IO 次数增加,进而导致查询性能的下降:高并发访问请求也使得集中式数

  • SpringBoot整合Mybatis自定义拦截器不起作用的处理方案

    目录 SpringBoot整合Mybatis自定义拦截器不起作用 1. 原始的读取mybatis-config.xml文件 2. 与SpringBoot容器整合 2.1 mybatis的自动装载 3. 在mybatis-config.xml配置又放入Spring容器 SpringBoot 自定义Mybatis拦截器 第一种 第二种 第三种 SpringBoot整合Mybatis自定义拦截器不起作用 Mybatis插件生效的方式: 1. 原始的读取mybatis-config.xml文件 该方式和

  • Springboot自定义mybatis拦截器实现扩展

    前言 相信大家对拦截器并不陌生,对mybatis也不陌生. 有用过pagehelper的,那么对mybatis拦截器也不陌生了,按照使用的规则触发sql拦截,帮我们自动添加分页参数 . 那么今天,我们的实践 自定义mybatis拦截器也是如此, 本篇文章实践的效果: 针对一些使用 单个实体类去接收返回结果的 mapper方法,我们拦截检测,如果没写 LIMIT 1 ,我们将自动帮忙填充,达到查找单条数据 效率优化的效果. ps: 当然,跟着该篇学会了这个之后,那么可以扩展的东西就多了,大家按照自

  • springboot配置多数据源后mybatis拦截器失效的解决

    目录 1. 解析配置文件初始化数据源 2. 定义数据源枚举类型 3. TheadLocal保存数据源类型 4. 自定义sqlSessionProxy 5. 自定义路由 6. 定义切面,dao层定义切面 7. 最后在写库增加事务管理 8. 在配置文件中增加数据源配置 配置文件是通过springcloudconfig远程分布式配置.采用阿里Druid数据源.并支持一主多从的读写分离.分页组件通过拦截器拦截带有page后缀的方法名,动态的设置total总数. 1. 解析配置文件初始化数据源 @Conf

  • 在springboot中如何给mybatis加拦截器

    目录 1.实现Interceptor接口,并添加拦截注解 @Intercepts 1.在mybatis中可被拦截的类型有四种(按照拦截顺序) 2.各个参数的含义 2.在配置文件中添加拦截器 (1)第一种 (2)第二种 (3)第三种 拦截器的作用就是我们可以拦截某些方法的调用,在目标方法前后加上我们自己逻辑 Mybatis拦截器设计的一个初衷是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑. mybatis 自定义拦截器 1.实现Interceptor 接口,并添加拦截注

  • mybatis 通过拦截器打印完整的sql语句以及执行结果操作

    开发过程中,如果使用mybatis做为ORM框架,经常需要打印出完整的sql语句以及执行的结果做为参考. 虽然mybatis结合日志框架可以做到,但打印出来的通常都是sql和参数分开的. 有时我们需要调试这条sql的时候,就需要把参数填进去,这样未免有些浪费时间. 此时我们可以通过实现mybatis拦截器来做到打印带参数的完整的sql,以及结果通过json输出到控制台. 直接看代码和使用方法吧: MyBatis拦截器打印不带问号的完整sql语句拦截器 import java.text.DateF

  • 浅析JAVA中过滤器、监听器、拦截器的区别

    1.过滤器:所谓过滤器顾名思义是用来过滤的,在java web中,你传入的request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的action进行业务逻辑,比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet或者struts的action前统一设置字符集,或者去除掉一些非法字符(聊天室经常用到的,一些骂人的话).filter 流程是线性的, url传来之后,检查之后,可保持原来的流程

  • MyBatis Excutor 拦截器的巧妙用法

    这里要讲的巧妙用法是用来实现在拦截器中执行额外 MyBatis 现有方法的用法. 并且会提供一个解决拦截Executor时想要修改MappedStatement时解决并发的问题. 这里假设一个场景: 实现一个拦截器,记录 MyBatis 所有的 insert,update,delete 操作,将记录的信息存入数据库. 这个用法在这里就是将记录的信息存入数据库. 实现过程的关键步骤和代码: 1.首先在某个 Mapper.xml 中定义好了一个往日志表中插入记录的方法,假设方法为id="insert

  • 详解SpringBoot开发使用@ImportResource注解影响拦截器

    问题描述 今天在给SpringBoot项目配置拦截器的时候发现怎么都进不到拦截器的方法里面,在搜索引擎上看了无数篇关于配置拦截器的文章都没有找到解决方案. 就在我准备放弃的时候,在 CSDN 上发现了一篇文章,说的是SpringBoot 用了@ImportResource 配置的拦截器就不起作用了.于是我就赶紧到Application启动类看了一眼,果然项目中使用了@ImportResource 注解用于配置系统的参数. 代码如下: 启动类配置 package com.xx.xxx; impor

  • Mybatis Plugin拦截器开发过程详解

    这篇文章主要介绍了Mybatis Plugin拦截器开发过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.Plugin MyBatis 允许使用插件来拦截的方法调用包括: • Executor (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed) • ParameterHandler (getParameterObject,

  • Mybatis自定义拦截器和插件开发详解

    前言 在Spring中我们经常会使用到拦截器,在登录验证.日志记录.性能监控等场景中,通过使用拦截器允许我们在不改动业务代码的情况下,执行拦截器的方法来增强现有的逻辑.在mybatis中,同样也有这样的业务场景,有时候需要我们在不侵入原有业务代码的情况下拦截sql,执行特定的某些逻辑.那么这个过程应该怎么实现呢,同样,在mybatis中也为开发者预留了拦截器接口,通过实现自定义拦截器这一功能,可以实现我们自己的插件,允许用户在不改动mybatis的原有逻辑的条件下,实现自己的逻辑扩展. 本文将按

  • SpringBoot面试突击之过滤器和拦截器区别详解

    目录 实现过滤器和拦截器 a) 实现过滤器 b) 实现拦截器 过滤器 VS 拦截器 1.出身不同 2.触发时机不同 3.实现不同 4.支持的项目类型不同 5.使用的场景不同 总结 实现过滤器和拦截器 首先,我们先来看一下二者在 Spring Boot 项目中的具体实现,这对后续理解二者的区别有很大的帮助. a) 实现过滤器 过滤器可以使用 Servlet 3.0 提供的 @WebFilter 注解,配置过滤的 URL 规则,然后再实现 Filter 接口,重写接口中的 doFilter 方法,具

随机推荐