• 4. 插件开发原理


    4. 插件开发原理

    插件原理没有搞懂, 不要进行插件的开发, 容易导致很严重的问题

    1. 插件概述

    ​ 插件是用来改变或者扩展mybatis的原有的功能,mybaits的插件就是通过继承Interceptor拦截器实现的;在没有完全理解插件之前禁止使用插件对mybaits进行扩展,又可能会导致严重的问题;

    mybatis中能使用插件进行拦截的接口和方法如下:

    • Executor(update、query 、 flushStatment 、 commit 、 rollback 、 getTransaction 、 close 、 isClose)
    • StatementHandler(prepare 、 paramterize 、 batch 、 update 、 query)
    • ParameterHandler( getParameterObject 、 setParameters )
    • ResultSetHandler( handleResultSets 、 handleCursorResultSets 、 handleOutputParameters )

    2. 插件开发快速入门

    定义一个阈值,当查询操作运行时间超过这个阈值记录日志供运维人员定位慢查询,插件实现步骤:

    1.实现Interceptor接口方法

    2.确定拦截的签名

    3.在配置文件中配置插件

    4.运行测试用例

    org.apache.ibatis.plugin.Interceptor

    public interface Interceptor {
    
        //执行拦截逻辑的方法
        Object intercept(Invocation invocation) throws Throwable;
    
        //target是被拦截的对象,它的作用就是给被拦截的对象生成一个代理对象
        Object plugin(Object target);
    
        //读取在plugin中设置的参数
        void setProperties(Properties properties);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    实现代码

    @Intercepts(
        {@Signature(type = StatementHandler.class, 
                    method = "query", 
                    args = {Statement.class, ResultHandler.class})
        //	@Signature(type=StatementHandler.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
    })
    public class ThresholdInterceptor implements Interceptor {
    
        private long threshold;
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            long begin = System.currentTimeMillis();
            Object ret = invocation.proceed();
            long end = System.currentTimeMillis();
            long runTime = end - begin;
            if (runTime >= threshold) {
                Object[] args = invocation.getArgs();
                Statement stat = (Statement)args[0];
                MetaObject metaObjectStat = SystemMetaObject.forObject(stat);
                PreparedStatementLogger statementLogger = 
                    (PreparedStatementLogger)metaObjectStat.getValue("h");
                Statement statement = statementLogger.getPreparedStatement();
                System.out.println("sql语句:“" + statement.toString() + "”执行时间为:" 
                                   + runTime + "毫秒,已经超过阈值!");
            }
            return ret;
        }
    
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this); // 封装
        }
    
        @Override
        public void setProperties(Properties properties) { // 读取参数配置
            this.threshold = Long.valueOf(properties.getProperty("threshold"));
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    配置

    <plugins>
        <plugin interceptor="com.len.mybatis.Interceptors.ThresholdInterceptor">
            <property name="threshold" value="10"/>
        plugin>
    plugins>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    源码分析之责任链模式

    责任链模式:就是把一件工作分别经过链上的各个节点,让这些节点依次处理这个工作;和装饰器模式不同,每个节点都知道后继者是谁;适合为完成同一个请求需要多个处理类的场景;

    image-20220703213826795

    要素分析

    • Handler:定义了一个处理请求的标准接口;
    • ConcreteHandler:具体的处理者,处理它负责的部分,根据业务可以结束处理流程,也可以将请求转发给它的后继者;
    • client :发送者,发起请求的客户端;

    责任链模式优点:

    • 降低耦合度。它将请求的发送者和接收者解耦。
    • 简化了对象。使得对象不需要知道链的结构。
    • 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
    • 增加新的请求处理类很方便。

    3. mybatis插件模块源码分析

    参考连接

    Mybatis-PageHelper/Interceptor.md at master · pagehelper/Mybatis-PageHelper · GitHub

    1.插件的初始化 (XMLConfigBuilder.pluginElement)

    2.插件的加载 (Configuration.new*方法,四大对象的创建)

    3.插件的调用 (Plugin. wrap、 Plugin. invoke)

    public class Configuration {
    	/*插件集合*/
    	protected final InterceptorChain interceptorChain = new InterceptorChain();
    	
        public List<Interceptor> getInterceptors() {
            return interceptorChain.getInterceptors();
        }
        
        public void addInterceptor(Interceptor interceptor) {
            interceptorChain.addInterceptor(interceptor);
        }
    }	
    
    public class InterceptorChain {
    	// 使用list数据结构, 所以责任链的顺序就是配置的顺序
        private final List<Interceptor> interceptors = new ArrayList<>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image-20220703214714358

    org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor 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 (cacheEnabled) {
            executor = new CachingExecutor(executor);
        }
        //通过interceptorChain遍历所有的插件为executor增强,添加插件的功能
        executor = (Executor)interceptorChain.pluginAll(executor);
        return executor;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    public class InterceptorChain {
    
        private final List<Interceptor> interceptors = new ArrayList<>();
    
        public Object pluginAll(Object target) {
            for (Interceptor interceptor : interceptors) {
                target = interceptor.plugin(target);
            }
            return target;
        }
    
        public void addInterceptor(Interceptor interceptor) {
            interceptors.add(interceptor);
        }
    
        public List<Interceptor> getInterceptors() {
            return Collections.unmodifiableList(interceptors);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    插件什么时候被代理的?

    org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration

    private void parseConfiguration(XNode root) {
        try {
    ...
            //解析节点
            pluginElement(root.evalNode("plugins"));
    ...
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "
                                       + e, e);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    private void pluginElement(XNode parent) throws Exception {
        if (parent != null) {
            //遍历所有的插件配置
            for (XNode child : parent.getChildren()) {
                //获取插件的类名
                String interceptor = child.getStringAttribute("interceptor");
                //获取插件的配置
                Properties properties = child.getChildrenAsProperties();
                //实例化插件对象
                Interceptor interceptorInstance = (Interceptor)resolveClass(interceptor).newInstance();
                //设置插件属性
                interceptorInstance.setProperties(properties);
                //将插件添加到configuration对象,底层使用list保存所有的插件并记录顺序
                configuration.addInterceptor(interceptorInstance);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这里并没有生成代理, 说明在解析plugins元素时候并没有生成代理, 那么是在使用时候被代理了

    org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    ...
        //通过interceptorChain遍历所有的插件为executor增强,添加插件的功能
        executor = (Executor)interceptorChain.pluginAll(executor);
        return executor;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public class InterceptorChain {
    
        private final List<Interceptor> interceptors = new ArrayList<>();
    
        public Object pluginAll(Object target) {
            for (Interceptor interceptor : interceptors) {
                target = interceptor.plugin(target); // 重点是这个代码
            }
            return target;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    com.len.mybatis.Interceptors.ThresholdInterceptor

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this); // 使用了mybatis提供的工具生成代理
    }
    
    • 1
    • 2
    • 3
    • 4

    所以这个代理生成方式就是, 具体实现的方法需要提供代理生成的方法plugin, mybatis在使用插件时候会通过责任链的pluginAll方法回调这个plugin生成代理

    4. Mybatis分页插件PageHelper

    1. 分页插件的使用;
      中文文档:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md
      使用手册:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md

    2. 分页插件的注意事项;
      注意事项:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/Important.md

    3. 使用

      依赖

    <dependency>
        <groupId>com.github.pagehelpergroupId>
        <artifactId>pagehelperartifactId>
        <version>5.1.4version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    配置

    <plugins>
        
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            
            <property name="pageSizeZero" value="true"/>
    	plugin>
    plugins>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    @Test
    public void testManyParamQuery() {
        // 2.获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 3.获取对应mapper
        TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
    
        String email = "qq.com";
        Byte sex = 1;
    
        // 第一种方式使用map
        //    Map params = new HashMap();
        //    params.put("email", email);
        //    params.put("sex", sex);
        //    List list1 = mapper.selectByEmailAndSex1(params);
        //    System.out.println(list1.size());
    
        // 第二种方式直接使用参数
        Page<TUser> startPage = PageHelper.startPage(2, 3);
        List<TUser> list2 = mapper.selectByEmailAndSex2(email, sex);
        System.out.println(list2.size());
        //    return startPage;
    
        // 第三种方式用对象
        //    EmailSexBean esb = new EmailSexBean();
        //    esb.setEmail(email);
        //    esb.setSex(sex);
        //    List list3 = mapper.selectByEmailAndSex3(esb);
        //    System.out.println(list3.size());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    注意事项:

    Page startPage = PageHelper.startPage(2, 3);
    List list2 = mapper.selectByEmailAndSex2(email, sex);

    1. 这两句要连着, 不要加任何语句

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b4nDak4T-1659111130373)(C:/Users/hgy/AppData/Roaming/Typora/typora-user-images/image-20220704005549259.png)]

    image-20220704005631816

    1. 不支持嵌套结果集的查询
  • 相关阅读:
    王道数据结构——顺序循环队列
    7-143 降价提醒机器人
    kubernetes6 (ingress)
    暑假加餐|有钱人和你想的不一样(第17天)+高比例风电电力系统储能运行及配置分析(Matlab代码实现)
    PHP Curl请求封装
    Python应用—车辆统计(Opencv)
    kubernetes镜像下载页,离线安装k8s的资源
    代码随想录训练营二刷第二十四天 | 77. 组合
    【人工智能数学:01概率论】(2) 离散型概率空间
    Spring Boot框架介绍
  • 原文地址:https://blog.csdn.net/yin18827152962/article/details/126066215