美文网首页
MyBatis插件

MyBatis插件

作者: Doooook | 来源:发表于2020-07-26 23:00 被阅读0次

    关于MyBatis四大对象及插件原理请参看https://www.jianshu.com/p/f704c9ae600ehttps://www.cnblogs.com/chenpi/p/10498921.html,这里我们使用插件原理实现MySQL分页,限制查询记录数。

    插件开发实现步骤

    1. 确定需要拦截的签名
      正如MyBatis插件可以拦截四大对象中的任意一个一样。从Plugin源码中我们可以看到它需要注册签名才能够运行插件。签名需要确定一些要素。
      首先要根据功能来确定你需要拦截什么对象。
    • Executor是执行SQL的全过程,包括组装参数,组装结果集返回和执行SQL过程,都可以拦截,较为广泛,我们一般用的不算太多。
    • StatementHandler是执行SQL的过程,我们可以重写执行SQL的过程。这是我们最常用的拦截对象。
      ParameterHandler,很明显它主要是拦截执行SQL的参数组装,你可以重写组装参数规则。
    • ResultSetHandler用于拦截执行结果的组装,你可以重写组装结果的规则。


      image.png
    image.png
    1. 拦截方法和参数
      当你确定了需要拦截什么对象,接下来就要确定需要拦截什么方法及方法的参数,这些都是在你理解了MyBatis四大对象运作的基础上才能确定的。
      查询的过程是通过Executor调度StatementHandler来完成的。调度StatementHandler的prepare方法预编译SQL,于是我们需要拦截的方法便是prepare方法,在此之前完成SQL的重新编写。


      image.png

    实现拦截方法

    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.plugin.*;
    import org.apache.ibatis.reflection.MetaObject;
    import org.apache.ibatis.reflection.SystemMetaObject;
    
    import java.sql.Connection;
    import java.util.Properties;
    
    /**
     * @author: Jay Mitter
     * @date: 2020-07-26 17:26
     * @description:
     */
    @Intercepts({@Signature(
            type = StatementHandler.class, // 确定需要拦截的对象
            method = "prepare",  // 确定需要拦截的对象方法
            args = {Connection.class, Integer.class} // 拦截方法的参数
    )})
    public class QueryLimitPlugin implements Interceptor {
    
        /**
         * 默认限制查询返回行数
         */
        private int limit;
    
        private String dbType;
    
        /**
         * 限制表中间别名。避免表重名,所以起得怪些
         */
        private static final String LMT_TABLE_NAME = "limit_Table_Name_xxx";
    
        /**
         * 代理拦截对象方法的内容
         * 这里的intercept犯法就会覆盖StatementHandler的prepare方法,我们先从代理对象分离出真实的对象,然后根据需要修改的SQL,来达到限制返回行数的需求。
         * 最后使用invocation.proceed()来调度真实的StatementHandler的prepare方法来完成SQL的预编译,最后需要在MyBatis的配置文件洪配置才能运行这个插件
         * <plugins>
         *      <plugin interceptor="com.pengjs.kkb.mybatis.plugin.QueryLimitPlugin">
         *          <property name="dbType" value="mysql"/>
         *          <property name="limit" value="10"/>
         *      </plugin>
         * </plugins>
         * @param invocation 责任链对象
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            // 取出被拦截对象
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
            // 分离代理对象,从而形成多次代理,通过两次循环最原始的被代理类,MyBatis使用JDK代理
            while (metaObject.hasGetter("h")) {
                Object object = metaObject.getValue("h");
                metaObject = SystemMetaObject.forObject(object);
            }
            // 分离最后一个代理的目标类
            while (metaObject.hasGetter("target")) {
                Object object = metaObject.getValue("target");
                metaObject = SystemMetaObject.forObject(object);
            }
            // 取出即将要执行的SQL
            String sql = (String) metaObject.getValue("delegate.boundSql.sql");
            String limitSql;
            // 判断参数是不是MySQL数据库且SQL有没有被插件重写过
            if ("mysql".equals(this.dbType) && !sql.contains(LMT_TABLE_NAME)) {
                // 去掉前后空格
                sql = sql.trim();
                // 将参数写入SQL,相当于子查询
                limitSql = "select * from (" + sql + ") " + LMT_TABLE_NAME + " limit " + limit;
                // 重新要执行SQL
                metaObject.setValue("delegate.boundSql.sql", limitSql);
            }
            // 调用原来你对象的方法,进入责任链的下一层级
            return invocation.proceed();
        }
    
        /**
         * 生成对象的代理,这里常用MyBatis提供的Plugin的wrap方法
         * @param target 被代理的对象
         * @return
         */
        @Override
        public Object plugin(Object target) {
            // 使用MyBatis默认的类生成代理对象,JDK动态代理
            // 插件最终会进入plugin的invoke方法,在invoke方法中最终还是使用到了拦截器的intercept方法,如下:
            // return interceptor.intercept(new Invocation(target, method, args));
            return Plugin.wrap(target, this);
        }
    
        /**
         * 获取插件配置的属性,我们在MyBatis的配置文件中配置的属性
         * @param properties 是MyBatis配置的参数
         */
        @Override
        public void setProperties(Properties properties) {
            String strLimit = properties.getProperty("limit", "50");
            this.limit = Integer.parseInt(strLimit);
            // 这里我们读取设置的数据库类型
            this.dbType = properties.getProperty("dbtType", "mysql");
        }
    }
    

    配置和运行

    image.png

    观察日志发现预编译SQL已经经过插件按照我们的规则做了修改:


    image.png

    相关文章

      网友评论

          本文标题:MyBatis插件

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