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

        ......

  • 相关阅读:
    C++学习9 this指针详解
    福建省第八届 Triangles
    UVA 11584 Partitioning by Palindromes
    POJ 2752 Seek the Name, Seek the Fame
    UVA 11437 Triangle Fun
    UVA 11488 Hyper Prefix Sets (字典树)
    HDU 2988 Dark roads(kruskal模板题)
    HDU 1385 Minimum Transport Cost
    HDU 2112 HDU Today
    HDU 1548 A strange lift(最短路&&bfs)
  • 原文地址:https://www.cnblogs.com/h--d/p/10934748.html
Copyright © 2011-2022 走看看