zoukankan      html  css  js  c++  java
  • 【Mybatis】MyBatis之插件开发(十)

    MyBatis插件开发原理

      MyBatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变MyBatis的默认行为(诸如SQL重写之类的),由于插件会深入到MyBatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。

      MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现目标对象在执行目标方法之前进行拦截的效果。
      插件介入指的是:创建过程中都会涉及到调用interceptChain.pluginAll()方法对四大对象进行重新包装,返回一个代理对象。
      MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用,默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

      1、拦截执行器的方法:Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
      2、拦截参数的处理:ParameterHandler(getParameterObject, setParameters)
      3、拦截结果集的处理:ResultSetHandler(handleResultSets, handleOutputParameters)
      4、拦截Sql语法构建的处理:StatementHandler(prepare, parameterize, batch, update, query)

      默认情况下,Mybatis允许使用插件来拦截的接口和方法包括以下几个:

      插件开发是基于动态代理实现的,所有有必要对动态代理有所了解。

      我们可以看一下MyBatis是怎么创建这四大接口对象的。找到源码BaseStatementHandler  

    1 this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    2 this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

      进入configuration类,下面几处都是在创建newParameterHandler,ResultSetHandler,StatementHandler这几个对象,在调用的过程中,大家都看到了都使用了interceptorChain.pluginAll方法分别对每一个对象进行了重新包装并返回

     1 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
     2     ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
     3     parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
     4     return parameterHandler;
     5 }
     6  
     7 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
     8       ResultHandler resultHandler, BoundSql boundSql) {
     9     ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    10     resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    11     return resultSetHandler;
    12 }
    13  
    14 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    15     StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    16     statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    17     return statementHandler;
    18 }

      点进interceptorChain.pluginAll方法里面

     1 /**
     2 *每一个拦截器对目标类都进行一次代理
     3 *@target
     4 *@return 层层代理后的对象
     5 **/
     6 public Object pluginAll(Object target) {
     7     for (Interceptor interceptor : interceptors) {
     8       target = interceptor.plugin(target);
     9     }
    10     return target;
    11 
    12 }

      这一段代码可以看到:获取所有的Interceptor(拦截器),我们如果需要自定义拦截器就得实现Interceptor这个接口。然后调用interceptor.plugin(target);返回target包装之后的对象。

      所以,我们可以使用插件为目标对象创建一个代理对象,这Spring的AOP一样,其实都是动态代理,面向切面的编程。

    MyBatis插件开发 

      下面我们通过案例为StatementHandler创建代理对象

      1、新建一个maven工程,引入mybatis依赖及相关数据库依赖

        

      2、新建一个MyFirstPlugin拦截器类,并且实现Interceptor接口

     1 package com.test.mybatis.plugin;
     2 
     3 import java.util.Properties;
     4 
     5 import org.apache.ibatis.executor.statement.StatementHandler;
     6 import org.apache.ibatis.plugin.Interceptor;
     7 import org.apache.ibatis.plugin.Intercepts;
     8 import org.apache.ibatis.plugin.Invocation;
     9 import org.apache.ibatis.plugin.Plugin;
    10 import org.apache.ibatis.plugin.Signature;
    11 
    12 /**
    13  * 完成插件签名:
    14  *        告诉MyBatis当前插件用来拦截哪个对象的哪个方法
    15  *    @Intercepts(org.apache.ibatis.plugin.Intercepts)和
    16  *    签名注解@Signature(org.apache.ibatis.plugin.Signature),这两个注解用来配置拦截器要拦截的接口的方法。
    17  *
    18  *    @Intercepts注解中的属性是一个@Signature(签名)数组,可以在同一个拦截器中同时拦截不同的接口和方法。
    19  *
    20  *  @Signature中
    21  *      type:设置拦截的接口,可选值是4个:Executor、ParameterHandler、ResultSetHandler、StatementHandler
    22  *      method:设置拦截接口中的方法名,需要和接口匹配
    23  *      args:设置拦截方法的参数类型数组,通过方法名和参数类型可以确定唯一一个方法
    24  */
    25 @Intercepts(
    26         {
    27             @Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
    28         })
    29 public class MyFirstPlugin implements Interceptor {
    30 
    31     /**
    32      * intercept:拦截: 拦截目标对象的目标方法的执行;
    33      */
    34     public Object intercept(Invocation invocation) throws Throwable {
    35         // TODO Auto-generated method stub
    36         System.out.println("MyFirstPlugin...intercept:" + invocation.getMethod());
    37         // 执行目标方法
    38         Object proceed = invocation.proceed();
    39         // 返回执行后的返回值
    40         return proceed;
    41     }
    42 
    43     /**
    44      * plugin: 包装目标对象的:包装:为目标对象创建一个代理对象
    45      */
    46     public Object plugin(Object target) {
    47         // TODO Auto-generated method stub
    48         // 我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
    49         System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象" + target);
    50         Object wrap = Plugin.wrap(target, this);
    51         // 返回为当前target创建的动态代理
    52         return wrap;
    53     }
    54 
    55     /**
    56      * setProperties:
    57      *         将插件注册时 的property属性设置进来
    58      */
    59     public void setProperties(Properties properties) {
    60         // TODO Auto-generated method stub
    61         System.out.println("插件配置的信息:" + properties);
    62     }
    63 
    64 }

       3、在mybatis的全局配置文件中配置

    1 <!--plugins:注册插件 -->
    2 <plugins>
    3     <plugin interceptor="com.test.mybatis.plugin.MyFirstPlugin">
    4         <property name="username" value="root" />
    5         <property name="password" value="123456" />
    6     </plugin>
    7 </plugins>

       4、编辑测试类TestMybatis.java

     1 public static void main(String[] args) throws IOException {
     2 
     3     // 获取SqlSessionFactory
     4     InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
     5     SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
     6 
     7     SqlSession session = sqlSessionFactory.openSession();
     8     try {
     9         EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
    10 
    11         Employee employee01 = mapper.selectByPrimaryKey(1);
    12         System.out.println(employee01.getId());
    13 
    14     } finally {
    15         session.close();
    16     }
    17 }

      5、运行结果如下:

        

    插件开发总结

      插件开发步骤如下

        1、编写插件实现Interceptor接口,并使用@Intercepts注解完成插件签名

    1 @Intercepts({                                  @Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
    2 })
    3 public class MyFirstPlugin implements Interceptor {

        2、在全局配置文件中注册插件

    1 <!--plugins:注册插件 -->
    2 <plugins>
    3     <plugin interceptor="com.test.mybatis.plugin.MyFirstPlugin">
    4         <property name="username" value="root" />
    5         <property name="password" value="123456" />
    6     </plugin>
    7 </plugins>

      插件的原理

        按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理
        多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹;形成代理链
        目标方法执行时依次从外到内执行插件的intercept方法

      插件的作用    

        1、可以统计SQL的执行时间

        2、可以进行分页操作,如插件:Mybatis-PageHelper

        ......

  • 相关阅读:
    Code Chef December Challenge 2018题解
    UOJ#419. 【集训队作业2018】圆形(格林公式)
    BZOJ2178: 圆的面积并(格林公式)
    LOJ#3052. 「十二省联考 2019」春节十二响(启发式合并)
    LOJ#3048. 「十二省联考 2019」异或粽子(trie树+堆)
    Code Chef MINPOLY(计算几何+dp)
    LOJ#3088. 「GXOI / GZOI2019」旧词(树剖+线段树)
    LOJ#3087. 「GXOI / GZOI2019」旅行者(最短路)
    P5816 [CQOI2010]内部白点
    P5590 赛车游戏
  • 原文地址:https://www.cnblogs.com/h--d/p/10934748.html
Copyright © 2011-2022 走看看