精华内容
下载资源
问答
  • 1.网上搜索了很多,几乎都是能修改sql, 但是修改后的sql不生效,还是执行原来的sql. 2.这个版本亲测可以生效。 3.支持分页查询
  • 下面小编就为大家带来一篇MyBatis拦截器:给参数对象属性赋值的实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 主要给大家介绍了关于mybatis拦截器实现通用权限字段添加的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用mybatis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
  • mybatis拦截器的完整实现,test.sql是数据库测试脚本,主要目的是生成mybatis最终执行的sql语句,并打印出来,方便调试。 基于此,可以实现自动化分页。
  • 通过mybatis拦截器,实现为所有sql(或指定sql) 统一添加查询条件,譬如通过线程变量传递某参数(日期),来实现对指定参数的数据筛选,而不需要在每个查询前,手动将该条件注入到查询中。因该资料网络较少,故特此...
  • MyBatis拦截器分页与动态修改SQL及其参数值 提取SQL Like 字段
  • 数据修改与删除日志记录demo @Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
  • Mybatis拦截器记录数据更新历史记录到MongoDB的源码,另外需要配置拦截器到mybatis配置文件中。
  • 使用maven和jdk1.8 项目导入直接更改数据库连接即可使用
  • mybatis拦截器

    2018-11-09 17:52:29
    MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。
  • mybatis 拦截器

    2018-06-26 15:42:38
    mybatis拦截器以及JSqlParser解析sql和生成一个新的Sqlsource
  • 主要介绍了java利用mybatis拦截器统计sql执行时间示例,该拦截器拦截mybatis的query和update操作,能统计sql执行时间
  • Mybatis拦截器

    万次阅读 多人点赞 2019-04-16 22:31:18
    Mybatis拦截器介绍        Mybatis拦截器设计的初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。通过Mybatis拦截器我们可以拦截某些方法的调用,我们可以选择...

    一 Mybatis拦截器介绍

           Mybatis拦截器设计的初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。通过Mybatis拦截器我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。所以Mybatis拦截器的使用范围是非常广泛的。

           Mybatis里面的核心对象还是比较多,如下:

    Mybatis核心对象解释
    SqlSession作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
    ExecutorMyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
    StatementHandler封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合
    ParameterHandler负责对用户传递的参数转换成JDBC Statement 所需要的参数
    ResultSetHandler负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
    TypeHandler负责java数据类型和jdbc数据类型之间的映射和转换
    MappedStatementMappedStatement维护了一条mapper.xml文件里面 select 、update、delete、insert节点的封装
    SqlSource负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
    BoundSql表示动态生成的SQL语句以及相应的参数信息
    ConfigurationMyBatis所有的配置信息都维持在Configuration对象之中

           Mybatis拦截器并不是每个对象里面的方法都可以被拦截的。Mybatis拦截器只能拦截Executor、ParameterHandler、StatementHandler、ResultSetHandler四个对象里面的方法。

    • Executor

           Mybatis中所有的Mapper语句的执行都是通过Executor进行的。Executor是Mybatis的核心接口。从其定义的接口方法我们可以看出,对应的增删改语句是通过Executor接口的update方法进行的,查询是通过query方法进行的。Executor里面常用拦截方法如下所示。

    public interface Executor {
    
       ...
    
        /**
         * 执行update/insert/delete
         */
        int update(MappedStatement ms, Object parameter) throws SQLException;
    
        /**
         * 执行查询,先在缓存里面查找
         */
        <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
    
        /**
         * 执行查询
         */
        <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
    
        /**
         * 执行查询,查询结果放在Cursor里面
         */
        <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
    
        ...
    
    
    }
    
    • ParameterHandler

           ParameterHandler用来设置参数规则,当StatementHandler使用prepare()方法后,接下来就是使用它来设置参数。所以如果有对参数做自定义逻辑处理的时候,可以通过拦截ParameterHandler来实现。ParameterHandler里面可以拦截的方法解释如下:

    public interface ParameterHandler {
    
     ...
    
     /**
      * 设置参数规则的时候调用 -- PreparedStatement
      */
     void setParameters(PreparedStatement ps) throws SQLException;
    
     ...
    
    
    }
    
    • StatementHandler

           StatementHandler负责处理Mybatis与JDBC之间Statement的交互。

    public interface StatementHandler {
    
        ...
    
        /**
         * 从连接中获取一个Statement
         */
        Statement prepare(Connection connection, Integer transactionTimeout)
                throws SQLException;
    
        /**
         * 设置statement执行里所需的参数
         */
        void parameterize(Statement statement)
                throws SQLException;
    
        /**
         * 批量
         */
        void batch(Statement statement)
                throws SQLException;
    
        /**
         * 更新:update/insert/delete语句
         */
        int update(Statement statement)
                throws SQLException;
    
        /**
         * 执行查询
         */
        <E> List<E> query(Statement statement, ResultHandler resultHandler)
                throws SQLException;
    
        <E> Cursor<E> queryCursor(Statement statement)
                throws SQLException;
    
        ...
    
    }
    

    一般只拦截StatementHandler里面的prepare方法。

           在Mybatis里面RoutingStatementHandler是SimpleStatementHandler(对应Statement)、PreparedStatementHandler(对应PreparedStatement)、CallableStatementHandler(对应CallableStatement)的路由类,所有需要拦截StatementHandler里面的方法的时候,对RoutingStatementHandler做拦截处理就可以了,如下的写法可以过滤掉一些不必要的拦截类。

    @Intercepts({
            @Signature(
                    type = StatementHandler.class,
                    method = "prepare",
                    args = {Connection.class, Integer.class}
            )
    })
    public class TableShardInterceptor implements Interceptor {
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            if (invocation.getTarget() instanceof RoutingStatementHandler) {
                // TODO: 做自己的逻辑
            }
            return invocation.proceed();
        }
    
        @Override
        public Object plugin(Object target) {
            // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数
            return (target instanceof RoutingStatementHandler) ? Plugin.wrap(target, this) : target;
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    }
    

    关于Statement、PreparedStatement和CallableStatement的一些区别。以及Statement和PreparedStatement相比PreparedStatement的优势在哪里。强烈建议大家去百度下。

    • ResultSetHandler

           ResultSetHandler用于对查询到的结果做处理。所以如果你有需求需要对返回结果做特殊处理的情况下可以去拦截ResultSetHandler的处理。ResultSetHandler里面常用拦截方法如下:

    public interface ResultSetHandler {
    
        /**
         * 将Statement执行后产生的结果集(可能有多个结果集)映射为结果列表
         */
        <E> List<E> handleResultSets(Statement stmt) throws SQLException;
        <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
    
        /**
         * 处理存储过程执行后的输出参数
         */
        void handleOutputParameters(CallableStatement cs) throws SQLException;
    
    }
    

    二 Mybatis拦截器的使用

           Mybatis拦截器的使用,分两步:自定义拦截器类、注册拦截器类。

    2.1 自定义拦截器类

           自定义的拦截器需要实现Interceptor接口,并且需要在自定义拦截器类上添加@Intercepts注解。

    2.1.1 Interceptor接口

           Interceptor接口里面就三个方法。如下所示:

    public interface Interceptor {
    
        /**
         * 代理对象每次调用的方法,就是要进行拦截的时候要执行的方法。在这个方法里面做我们自定义的逻辑处理
         */
        Object intercept(Invocation invocation) throws Throwable;
    
        /**
         * plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理
         *
         * 当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法 -- Plugin.wrap(target, this)
         * 当返回的是当前对象的时候 就不会调用intercept方法,相当于当前拦截器无效
         */
        Object plugin(Object target);
    
        /**
         * 用于在Mybatis配置文件中指定一些属性的,注册当前拦截器的时候可以设置一些属性
         */
        void setProperties(Properties properties);
    
    }
    

    2.1.2 @Intercepts注解

           Intercepts注解需要一个Signature(拦截点)参数数组。通过Signature来指定拦截哪个对象里面的哪个方法。@Intercepts注解定义如下:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Intercepts {
        /**
         * 定义拦截点
         * 只有符合拦截点的条件才会进入到拦截器
         */
        Signature[] value();
    }
    

            Signature来指定咱们需要拦截那个类对象的哪个方法。定义如下:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Signature {
        /**
         * 定义拦截的类 Executor、ParameterHandler、StatementHandler、ResultSetHandler当中的一个
         */
        Class<?> type();
    
        /**
         * 在定义拦截类的基础之上,在定义拦截的方法
         */
        String method();
    
        /**
         * 在定义拦截方法的基础之上在定义拦截的方法对应的参数,
         * JAVA里面方法可能重载,不指定参数,不晓得是那个方法
         */
        Class<?>[] args();
    }
    

           我们举一个例子来说明,比如我们自定义一个MybatisInterceptor类,来拦截Executor类里面的两个query。自定义拦截类MybatisInterceptor

    @Intercepts({
            @Signature(
                    type = Executor.class,
                    method = "query",
                    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
            ),
            @Signature(
                    type = Executor.class,
                    method = "update",
                    args = {MappedStatement.class, Object.class}
            )
    })
    public class MybatisInterceptor implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
    
            // TODO: 自定义拦截逻辑
    
        }
    
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this); // 返回代理类
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    }
    
    

    2.2 注册拦截器

           注册拦截器就是去告诉Mybatis去使用我们的拦截器。注册拦截器类非常的简单,在@Configuration注解的类里面,@Bean我们自定义的拦截器类。比如我们需要注册自定义的MybatisInterceptor拦截器。

    /**
     * mybatis配置
     */
    @Configuration
    public class MybatisConfiguration {
    
        /**
         * 注册拦截器
         */
        @Bean
        public MybatisInterceptor mybatisInterceptor() {
            MybatisInterceptor interceptor = new MybatisInterceptor();
            Properties properties = new Properties();
            // 可以调用properties.setProperty方法来给拦截器设置一些自定义参数
            interceptor.setProperties(properties);
            return interceptor;
        }
        
    
    }
    

    三 Mybatis拦截器实例-自定义拦截器

           上面讲了一大堆,最终的目的都是要使用上拦截器,接下来。我们通过几个简单的自定义拦截器来加深对Mybatis拦截器的理解。实例代码在链接地址:https://github.com/tuacy/microservice-framework 的 mybatis-interceptor module里面。

    3.1 日志打印

           自定义LogInterceptor拦截器,打印出我们每次sq执行对应sql语句。

    3.2 分页

           模仿pagehelper,咱们也来实现一个分页的拦截器PageInterceptor,该拦截器也支持自定义count查询。

    3.3 分表

           自定义拦截器TableShardInterceptor实现水平分表的功能。

    3.4 对查询结果的某个字段加密

           自定义拦截器EncryptResultFieldInterceptor对查询回来的结果中的某个字段进行加密处理。

    上面拦截器的实现,在github https://github.com/tuacy/microservice-framework 的 mybatis-interceptor module里面都能找到具体的实现。


           发现想把Mybatis拦截器的使用讲清楚还是比较难的,因为里面设计的到的东西太多了,用代码才是最好说话的,所以我在实例里面都尽可能的把注解写的很详细。希望能对大家有点帮助。

    展开全文
  • 本篇文章主要介绍了MyBatis拦截器实现分页功能实例,这里整理了详细的代码,有需要的小伙伴可以参考下。
  • Mybatis 拦截器

    2020-04-30 07:12:21
    文章目录拦截器拦截器接口setPropertiespluginintercept拦截器签名过程 拦截器 MyBatis 允许在己映射语句执行过程中的某一点进行拦截调用。默认情况下, MyBatis 允许 使用插件来拦截的接口和方法包括以下几个。 ...

    拦截器

    MyBatis 允许在己映射语句执行过程中的某一点进行拦截调用。默认情况下, MyBatis 允许
    使用插件来拦截的接口和方法包括以下几个。

    1. Executor ( update 、 query 、 flushStatements 、 commit 、 rollback 、getTransaction 、 close 、 isClosed)
    2. ParameterHandler ( getParameterObject 、 setParameters)
    3. ResultSetHandler ( handleResultSets 、 handleCursorResultSets 、handleOutputParameters)
    4. StatementHandler (prepare 、 parameterize 、 batch 、 update 、 query)

    这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。

    拦截器接口

    定义一个Mybatis拦截器 需要实现接口 Interceptor

    public interface Interceptor {
    
      Object intercept(Invocation invocation) throws Throwable;
    
      Object plugin(Object target);
    
      void setProperties(Properties properties);
    }
    

    上面三个方法最重要的是自定义的 intercept 方法,其他两个方法的实现都是通用的实现。

    setProperties
        public void setProperties(Properties properties0) {
            this.properties = properties0;
        }
    

    用于给自定义的拦截器传递一些自定的参数信息, 该方法会在拦截器初始化 后被调用。这样拦截器就可以通过 Properties 取得配置的参数值。

    <plugins>
      <plugin interceptor="org.mybatis.example.ExamplePlugin">
        <property name="someProperty" value="100"/>
      </plugin>
    </plugins>
    
    plugin

    这个方法的参数 target 就是拦截器要拦截的对象,该方法会在创建被拦截的接口实现类时被调用。该方法的实现很简单 ,只需要调用 MyBatis 提供的Plugin (org.apache.ibatis.plugin.Plugin )类的 wrap 静态方法就可以通过 Java 的动态代理拦截目标对象

     public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    

    Plugin .wrap 方法会自动判断拦截器的签名和被拦截对象的接口是否匹配,只有匹配的情况下才会使用动态代理拦截目标对象,因此在上面的实现方法中不必做额外的逻辑判断 。

    intercept

    intercept 方法是 MyBatis 运行时要执行的拦截方法。通过该方法的参数invocation 可以得到很多有用的信息,该参数的常用方法如下 。

     public Object intercept(Invocation invocation) throws Throwable {
    		//前置处理
    		//preHandler()
    
     		// 获取 被拦截器对象
            Object target = invocation.getTarget();
            // 获取 当前被拦截的方法
            Method method = invocation.getMethod();
            // 获取 当前被拦截方法的参数
            Object[] args = invocation.getArgs();
            // 执行拦截对象 方法
            //实际上执行了 method.invoke(target,args)
            Object result = invocation.proceed();
    
    		//后置处理
    		//postHandler()
            return result;
        }
    

    当配置多个拦截器时, MyBatis 会遍历所有拦截器,按顺序执行拦截器的 plugi口方法,被拦截的对象就会被层层代理。在执行拦截对象的方法时,会一层层地调用拦截器,拦截器通过 invocation.proceed()调用下一层的方法,直到真正的方法被执行。方法执行的结果会从最里面开始向外一层层返回,所 以如果存在按顺序配置的 A、 B 、 C 三个签名相同的拦截器,MyBaits 会按照 C>B>A>target.proceed()>A>B>C 的顺序执行。如果 A 、 B 、 C 签名不同,就会按照 MyBatis 拦截对象的逻辑执行。

    在这里插入图片描述

    拦截器签名

    上面提到了 Object plugin(Object target) 方法的实现,会判断拦截器签名和被拦截对象接口是否匹配,匹配的情况下会代理对象。

    所以Mybatis 拦截器除了需要实现Intercept接口之外,还需要拦截信息而这些信息存在注解中:

    1. @Intercepts(org.apache.ibatis.plugin.Intercepts)
    2. 签名注解@Signature(org.apache.ibatis.plugin.Signature),

    这两个注解用来配置拦截器要拦截的接口的方法。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Intercepts {
      Signature[] value();
    }
    

    拦截器注解接受多个签名注解Signature,所以我们可以在同 一个拦截器中同时拦截不同的接口和方法。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Signature {
      Class<?> type();
    
      String method();
    
      Class<?>[] args();
    }
    

    @Signature 注解包含以下三个属性。

    1. type : 设置拦截 的接口,可选值是前面提到的 4 个接口 。
    2. method : 设置拦截接口中的方法名 , 可选值是前面 4 个接口对应的方法,需要和接口匹配 。
    3. args : 设置拦截方法的参数类型数组 , 通过方法名和参数类型可以确定唯一一个方法 。

    可以看到注解 Signature 使用@Target({})修饰,所以它只能作为其它注解的一个成员。

    流程分析

    调用Configuration 对象的 addInterceptor 方法 添加自定义拦截器。

    【Configuration.java】

      // 保存添加的拦截器插件
      protected final InterceptorChain interceptorChain = new InterceptorChain();
    
      public void addInterceptor(Interceptor interceptor) {
        interceptorChain.addInterceptor(interceptor);
      }
    

    拦截器的定义可以通过两种方式:

    1. 通过XML 配置拦截器 。解析配置文件中的plugin 定义

      <plugins>
        <plugin interceptor="org.mybatis.example.ExamplePlugin">
          <property name="someProperty" value="100"/>
        </plugin>
      </plugins>
      
    2. 通过注解

      @Component
      @Intercepts(
      	{
      		@Signature()
      	}
      )
      public class ExamplePlugin implements Interceptor{
      }
      

    为什么我们可以针对Executor、StatementHandler、ResultSetHandler 做增强处理呢?首先我们需要知道Mybatis执行一条sql语句的大致流程:

    SqlSession -> Executor 调用 select、update方法 -> StatementHandler sql参数参数赋值 -> ResultSetHandler 结果映射。

    而在获取Executor、StatementHandler、ResultSetHandler 对象时会试图生成该对象的代理对象。

    [Configuration.java]

     public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
            ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
            ParameterHandler parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
            return parameterHandler;
        }
    
        public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
            ResultSetHandler resultSetHandler = mappedStatement.hasNestedResultMaps() ? new NestedResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds) : new FastResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
            ResultSetHandler resultSetHandler = (ResultSetHandler)this.interceptorChain.pluginAll(resultSetHandler);
            return resultSetHandler;
        }
    
        public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
            StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
            StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
            return statementHandler;
        }
    
    
     public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
            executorType = executorType == null ? this.defaultExecutorType : executorType;
            executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
            Object executor;
            if (ExecutorType.BATCH == executorType) {
                executor = new BatchExecutor(this, transaction);
            } else if (ExecutorType.REUSE == executorType) {
                executor = new ReuseExecutor(this, transaction);
            } else {
                executor = new SimpleExecutor(this, transaction);
            }
    
            if (this.cacheEnabled) {
                executor = new CachingExecutor((Executor)executor, autoCommit);
            }
    
            Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
            return executor;
        }
    

    上面的所有方法都调用了下面一个关键的语句。

    	// 返回代理对象(如果拦截器签名和被拦截对象匹配的话)
       Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
    

    调用 Mybatis 拦截器链 ,可以看到每个拦截器都会拦截被代理对象返回代理对象。 需要注意的是被拦截对象可能被多个拦截器代理。

    【InterceptorChain.java】

      private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
    
      /**
      * 判断拦截器的签名和被拦截对象的接口是否匹配,如果匹配返回代理对象
      */
      public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
          target = interceptor.plugin(target);
        }
        return target;
      }
      // 上面的两种自定的拦截器就是通过 InterceptorChain#addInterceptor 方法添加到interceptors 列表中的
      public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
      }
      
      public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
      }
    

    上面我们说过,Mybatis拦截器的plugin方法一般实现就是 使用Plugin类的静态方法wrap,这样才能被Mybatis框架感知到(当然我们也可以仿照Plugin类,实现一套自己的)。

     public Object plugin(Object target) {
     		// 返回代理对象(如果条件满足的话)
            return Plugin.wrap(target, this);
        }
    

    Plugin.wrap方法返回代理对象(如果拦截器签名和接口一致)

    【Plugin .java】

      public static Object wrap(Object target, Interceptor interceptor) {
      	/**
      	* 获取拦截器的所有签名。见名知意:获取签名的Map
      	*/
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        /**
        *查询  被拦截对象对应Class类的所有父接口 在Map中存在的匹配关系。
        */
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
       
        if (interfaces.length > 0) {
    	    /**
    	    * interfaces.length > 0 表示拦截器的签名和被拦截对象的接口匹配,那么生成代理对象
    	    */
          return Proxy.newProxyInstance(
              type.getClassLoader(),
              interfaces,
              new Plugin(target, interceptor, signatureMap));
        }
        /**
        *否则返回 原始对象
        */
        return target;
      }
    

    比较 拦截器的签名是否和被拦截对象的接口是否匹配。匹配成功话则返回代理对象。

      // 保存拦截器所有的签名
      private Map<Class<?>, Set<Method>> signatureMap;
      private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
      }
    
    

    还有一处值得留意的动态代理的InvocationHandler 是 Plugin 对象。

     //生成 动态代理对象
      Proxy.newProxyInstance(
              type.getClassLoader(),
              interfaces,
              new Plugin(target, interceptor, signatureMap));
    
      public class Plugin implements InvocationHandler
    

    这样调用代理对象的方法时,就会触发 invoke方法。 在invoke方法中没有做额外的处理,扩展操作交由 interceptor 完成。

    【Plugin.java】

      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          Set<Method> methods = signatureMap.get(method.getDeclaringClass());
          // 调用 上面四个接口中的方法则执行增强操作。
          //比如 Executor ( update 、 query 、 flushStatements 、 commit 、 rollback 、getTransaction 、 close 、 isClosed)
          if (methods != null && methods.contains(method)) {
            return interceptor.intercept(new Invocation(target, method, args));
          }
          // 调用代理对象的 toString、hashCode、equals方法也会触发 invoke 回调
          // 调用代理对象的方法不是 在拦截器签名中定义的方法
          return method.invoke(target, args);
        } catch (Exception e) {
          throw ExceptionUtil.unwrapThrowable(e);
        }
      }
    

    Mybatis 拦截器的实现是围绕类Plugin、接口 Interceptor展开的。

    在这里插入图片描述

    参考:

    1. Mybatis 中文文档
    2. Mybatis 从入门到精通
    展开全文
  • 主要介绍了简单了解mybatis拦截器实现原理及实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • Mybatis 如何防止 sql 注入?mybatis 拦截器了解过吗,应用场景是什么答案 Mybatis 如何防止 sql 注入?mybatis 拦截器了解过吗,应用场景是什么答案
  • NULL 博文链接:https://zhanghteye.iteye.com/blog/2373231
  • Mybatis拦截器实现分页

    2020-08-31 13:34:48
    本文介绍使用Mybatis拦截器,实现分页;并且在dao层,直接返回自定义的分页对象。具有很好的参考价值,下面跟着小编一起来看下吧
  • spring+springMVC+mybatis拦截器分页
  • 内含有mybatis 拦截器实现的分页代码,spring 的事务和aop 测试、和反射工具类
  • 文章目录1....MyBatis拦截器可以做的工作:SQL修改,分页操作,数据过滤,SQL执行时间性能监控等。 1. 基础介绍 1.1. 核心对象 从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个: Con


    MyBatis拦截器可以做的工作:SQL修改,分页操作,数据过滤,SQL执行时间性能监控等。

    1. 基础介绍

    1.1. 核心对象

    MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:

    • Configuration:初始化基础配置,比如MyBatis的别名等,一些重要的类型对象,如插件,映射器,ObjectFactorytypeHandler对象,MyBatis所有的配置信息都维持在Configuration对象之中。
    • SqlSessionFactorySqlSession工厂。
    • SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要的数据库增删改查功能。
    • ExecutorMyBatis的内部执行器,它负责调用StatementHandler操作数据库,并把结果集通过ResultSetHandler进行自动映射,另外,它还处理二级缓存的操作。
    • StatementHandlerMyBatis直接在数据库执行SQL脚本的对象。另外它也实现了MyBatis的一级缓存。
    • ParameterHandler:负责将用户传递的参数转换成JDBC Statement所需要的参数。是MyBatis实现SQL入参设置的对象。
    • ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合。是MyBatisResultSet集合映射成POJO的接口对象。
    • TypeHandler:负责Java数据类型和JDBC数据类型之间的映射和转换。
    • MappedStatementMappedStatement维护了一条<select|update|delete|insert>节点的封装。
    • SqlSource :负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回。
    • BoundSql:表示动态生成的SQL语句以及相应的参数信息。

    1.2. 执行过程

    2. 实现步骤

    • 写一个实现org.apache.ibatis.plugin.Interceptor接口的拦截器类,并实现其中的方法。
    • 添加@Intercepts注解,写上需要拦截的对象和方法,以及方法参数。
    • Spring项目注意添加@Component注解即可,使其成为Spring管理的一个Bean

    2.1. 添加注解

    MyBatis拦截器默认可以拦截的类型只有四种,即四种接口类型ExecutorStatementHandlerParameterHandlerResultSetHandler。对于我们的自定义拦截器必须使用MyBatis提供的@Intercepts注解来指明我们要拦截的是四种类型中的哪一种接口。

    注解描述
    @Intercepts标志该类是一个拦截器
    @Signature指明该拦截器需要拦截哪一个接口的哪一个方法

    @Signature注解的参数:

    参数描述
    type四种类型接口中的某一个接口,如Executor.class
    method对应接口中的某一个方法名,比如Executorquery方法。
    args对应接口中的某一个方法的参数,比如Executorquery方法因为重载原因,有多个,args就是指明参数类型,从而确定是具体哪一个方法。
    @Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
        @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}),
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
    })
    

    2.1.1. type

    MyBatis拦截器默认会按顺序拦截以下的四个接口中的所有方法:

    org.apache.ibatis.executor.Executor  //拦截执行器方法
    org.apache.ibatis.executor.statement.StatementHandler  //拦截SQL语法构建处理
    org.apache.ibatis.executor.parameter.ParameterHandler  //拦截参数处理
    org.apache.ibatis.executor.resultset.ResultSetHandler  //拦截结果集处理
    

    具体是拦截这四个接口对应的实现类:

    org.apache.ibatis.executor.CachingExecutor
    org.apache.ibatis.executor.statement.RoutingStatementHandler
    org.apache.ibatis.scripting.defaults.DefaultParameterHandler
    org.apache.ibatis.executor.resultset.DefaultResultSetHandler
    

    2.1.2. method

    这个可以根据MyBatis源码了解下。

    2.1.3. args

    根据参数类型区分重载的方法。

    2.2. 方法实现

    2.2.1. intercept

    进行拦截的时候要执行的方法。该方法参数Invocation类中有三个字段:

      private final Object target;
      private final Method method;
      private final Object[] args;
    

    可通过这三个字段分别获取下面的信息:

    Object target = invocation.getTarget();//被代理对象
    Method method = invocation.getMethod();//代理方法
    Object[] args = invocation.getArgs();//方法参数
    
    拦截接口接口实现类
    ExecutorCachingExecutor
    StatementHandlerRoutingStatementHandler
    ParameterHandlerDefaultParameterHandler
    ResultSetHandlerDefaultResultSetHandler

    2.2.2. plugin

    插件用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理,可以决定是否要进行拦截进而决定要返回一个什么样的目标对象,官方提供了示例:return Plugin.wrap(target, this);,可以在这个方法中提前进行拦截对象类型判断,提高性能:

        @Override
        public Object plugin(Object target) {
            //只对要拦截的对象生成代理
            if(target instanceof StatementHandler){
                //调用插件
                return Plugin.wrap(target, this);
            }
            return target;
        }
    

    MyBatis拦截器用到责任链模式+动态代理+反射机制;
    所有可能被拦截的处理类都会生成一个代理类,如果有N个拦截器,就会有N个代理,层层生成动态代理是比较耗性能的。而且虽然能指定插件拦截的位置,但这个是在执行方法时利用反射动态判断的,初始化的时候就是简单的把拦截器插入到了所有可以拦截的地方。所以尽量不要编写不必要的拦截器。另外我们可以在调用插件的地方添加判断,只要是当前拦截器拦截的对象才进行调用,否则直接返回目标对象本身,这样可以减少反射判断的次数,提高性能。

    2.2.3. setProperties

    如果我们拦截器需要用到一些变量参数,而且这个参数是支持可配置的,类似Spring中的@Value("${}")application.properties文件获取自定义变量属性,这个时候我们就可以使用这个方法。

    (1)在application.properties文件中添加配置:

    mybatis.config-location=classpath:mybatis-config.xml
    

    (2)在resources目录下添加mybatis-config.xml配置文件,并添加插件和属性配置。添加完需要注意去掉自定义MyBatis拦截器上的@Component注解,否则该拦截器相当于注册了两个,会执行两遍拦截方法。

    <?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>
        <plugins>
            <plugin interceptor="com.example.demo.mapper.plugin.MyPlugin">
                <property name="key1" value="value1"/>
                <property name="key2" value="value2"/>
                <property name="key3" value="value3"/>
            </plugin>
        </plugins>
    </configuration>
    

    (3)在拦截器插件的setProperties方法中进行。这些自定义属性参数会在项目启动的时候被加载。

        @Override
        public void setProperties(Properties properties) {
            System.out.println("key1=" + properties.getProperty("key1"));
            System.out.println("key2=" + properties.getProperty("key2"));
            System.out.println("key3=" + properties.getProperty("key3"));
        }
    

    3. 代码示例

    package com.example.demo.mapper.plugin;
    
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.plugin.*;
    import java.lang.reflect.Method;
    import java.sql.Connection;
    import java.util.Properties;
    
    @Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
    })
    public class MyPlugin implements Interceptor {
    
        Properties properties = null;
    
        /**
         * 拦截方法逻辑
         * 这里主要是通过反射去获取要执行的SQL相关信息,然后进行操作
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            Object target = invocation.getTarget();//被代理对象
            Method method = invocation.getMethod();//代理方法
            Object[] args = invocation.getArgs();//方法参数
            // do something ...... 方法拦截前执行代码块
            Object result = invocation.proceed();
            // do something .......方法拦截后执行代码块
            return result;
        }
    
        /**
         * 生成MyBatis拦截器代理对象
         */
        @Override
        public Object plugin(Object target) {
            if(target instanceof StatementHandler){
                //调用插件
                return Plugin.wrap(target, this);
            }
            return target;
        }
    
        /**
         * 设置插件属性(直接通过Spring的方式获取属性,所以这个方法一般也用不到)
         * 项目启动的时候数据就会被加载
         */
        @Override
        public void setProperties(Properties properties) {
            //赋值成员变量,在其他方法使用。
            this.properties = properties;
        }
    }
    
    展开全文
  • Mybatis拦截器失效

    2021-05-03 22:54:12
    Mybatis拦截器是采用的责任链模式,一般拦截器中intercept方法中最后执行 invocation.proceed() 方法,将拦截器责任链向后传递; 但是查看pageHelper源码可以发现,他的拦截器方法中并没有向后传递责任链,而是直接...

    现象:

    自定义插件注册成功,但是始终不进入拦截器方法;
    我的拦截器:
    在这里插入图片描述
    在这里插入图片描述

    排查

    Mybatis拦截器是采用的责任链模式,一般拦截器中intercept方法中最后执行 invocation.proceed() 方法,将拦截器责任链向后传递;

    但是查看pageHelper源码可以发现,他的拦截器方法中并没有向后传递责任链,而是直接执行了另一个query方法:

    • com.github.pagehelper.PageInterceptor#intercept
      在这里插入图片描述

    方案一

    不管插件执行顺序,按规范文档来配置自定义插件即可:(即两个query方法都拦截)
    在这里插入图片描述

    方案二

    想办法让自定义拦截器,在pageHelper拦截器之前执行,拦截第一个query方法:
    (或者配置在pagehelper之后执行,拦截第二个query方法也是可以的);

    所以需要使自定义拦截器在pagehelper之后添加;

    • 自定义插件配置类上添加注解: @AutoConfigureAfter({PageHelperAutoConfiguration.class})
    @Configuration
    @ConditionalOnBean({SqlSessionFactory.class})
    @AutoConfigureAfter({PageHelperAutoConfiguration.class})  // 保证在分页插件之后加载
    public class MybatisInterceptorConfig { …… }
    
    • 将该配置类设置为一个 auto-configuration 类
    # 创建src/main/resources/META-INF/spring.factories,声明该配置类即可
    
    # spring.factories文件中  # 自定义配置类的全路径自己修改
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.hx.springbootmybatis.order.mybatis.MybatisInterceptorConfig  
    

    方案三

    就是方案二括号中的方式:(或者配置在pagehelper之后执行,拦截第二个query方法也是可以的); 把方案二中的注解改一下:

    @AutoConfigureBefore({PageHelperAutoConfiguration.class})  // 保证在分页插件之前加载
    

    而且这样还可以捕获到分页语句构建之后的 sql 了!

    补充

    CachingExecutor中有2个query方法:
    在这里插入图片描述

    另外提一个问题:query方法中调用了同对象中的另一个query方法,会再次进入拦截器吗?
    答案:不会
    观察源码得知,mybatis拦截器的执行逻辑是这样的,所以更加肯定的得出结论:不会;
    在这里插入图片描述

    总结:

    Mybatis 插件的执行顺序有两种:
    1、不同拦截对象执行顺序,如下:
    	Executor` -> `StatementHandler` -> `ParameterHandler` -> `ResultSetHandler
    2、拦截相同对象执行顺序,取决于 mybatis-config.xml 中 <plugin> 配置顺序,越靠后,优先级越高。
    3、拦截相同对象执行顺序,如果是使用的配置类加载,则取决于配置类的加载顺序,加载顺序,越靠后,优先级越高;
    

    参考链接:

    1、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/

    2、https://zhuanlan.zhihu.com/p/266735787
    3、QueryInterceptor 规范


    展开全文
  • 上一篇中讲了mybatis拦截器的实现 这一篇扩展mybatis在拦截器中添加额外参数 在mybatis的mapper.xml文件中,我们可以使用#{}或${}的方式获取到参数,这些参数都需要提前我们在mapper.java接口文件中通过参数的方式传入...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 44,842
精华内容 17,936
关键字:

mybatis拦截器