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  

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

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

 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
} public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) 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) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}

  点进interceptorChain.pluginAll方法里面

 /**
*每一个拦截器对目标类都进行一次代理
*@target
*@return 层层代理后的对象
**/
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target; }

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

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

MyBatis插件开发 

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

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

    

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

 package com.test.mybatis.plugin;

 import java.util.Properties;

 import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature; /**
* 完成插件签名:
* 告诉MyBatis当前插件用来拦截哪个对象的哪个方法
* @Intercepts(org.apache.ibatis.plugin.Intercepts)和
* 签名注解@Signature(org.apache.ibatis.plugin.Signature),这两个注解用来配置拦截器要拦截的接口的方法。
*
* @Intercepts注解中的属性是一个@Signature(签名)数组,可以在同一个拦截器中同时拦截不同的接口和方法。
*
* @Signature中
* type:设置拦截的接口,可选值是4个:Executor、ParameterHandler、ResultSetHandler、StatementHandler
* method:设置拦截接口中的方法名,需要和接口匹配
* args:设置拦截方法的参数类型数组,通过方法名和参数类型可以确定唯一一个方法
*/
@Intercepts(
{
@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor { /**
* intercept:拦截: 拦截目标对象的目标方法的执行;
*/
public Object intercept(Invocation invocation) throws Throwable {
// TODO Auto-generated method stub
System.out.println("MyFirstPlugin...intercept:" + invocation.getMethod());
// 执行目标方法
Object proceed = invocation.proceed();
// 返回执行后的返回值
return proceed;
} /**
* plugin: 包装目标对象的:包装:为目标对象创建一个代理对象
*/
public Object plugin(Object target) {
// TODO Auto-generated method stub
// 我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象" + target);
Object wrap = Plugin.wrap(target, this);
// 返回为当前target创建的动态代理
return wrap;
} /**
* setProperties:
* 将插件注册时 的property属性设置进来
*/
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
System.out.println("插件配置的信息:" + properties);
} }

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

 <!--plugins:注册插件 -->
<plugins>
<plugin interceptor="com.test.mybatis.plugin.MyFirstPlugin">
<property name="username" value="root" />
<property name="password" value="123456" />
</plugin>
</plugins>

  4、编辑测试类TestMybatis.java

 public static void main(String[] args) throws IOException {

     // 获取SqlSessionFactory
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class); Employee employee01 = mapper.selectByPrimaryKey(1);
System.out.println(employee01.getId()); } finally {
session.close();
}
}

  5、运行结果如下:

    

插件开发总结

  插件开发步骤如下

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

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

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

 <!--plugins:注册插件 -->
<plugins>
<plugin interceptor="com.test.mybatis.plugin.MyFirstPlugin">
<property name="username" value="root" />
<property name="password" value="123456" />
</plugin>
</plugins>

  插件的原理

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

  插件的作用    

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

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

    ......

【Mybatis】MyBatis之插件开发(十)的更多相关文章

  1. MyBatis基础入门《十九》动态SQL(set,trim)

    MyBatis基础入门<十九>动态SQL(set,trim) 描述: 1. 问题 : 更新用户表数据时,若某个参数为null时,会导致更新错误 2. 分析: 正确结果: 若某个参数为nul ...

  2. MyBatis基础入门《十八》动态SQL(if-where)

    MyBatis基础入门<十八>动态SQL(if-where) 描述: 代码是在<MyBatis基础入门<十七>动态SQL>基础上进行改造的,不再贴所有代码,仅贴改动 ...

  3. MyBatis基础入门《十六》缓存

    MyBatis基础入门<十六>缓存 >> 一级缓存 >> 二级缓存 >> MyBatis的全局cache配置 >> 在Mapper XML文 ...

  4. MyBatis基础入门《十五》ResultMap子元素(collection)

    MyBatis基础入门<十五>ResultMap子元素(collection) 描述: 见<MyBatis基础入门<十四>ResultMap子元素(association ...

  5. MyBatis基础入门《十四》ResultMap子元素(association )

    MyBatis基础入门<十四>ResultMap子元素(association ) 1. id: >> 一般对应数据库中改行的主键ID,设置此项可以提高Mybatis的性能 2 ...

  6. MyBatis基础入门《十二》删除数据 - @Param参数

    MyBatis基础入门<十二>删除数据 - @Param参数 描述: 删除数据,这里使用了@Param这个注解,其实在代码中,不使用这个注解也可以的.只是为了学习这个@Param注解,为此 ...

  7. MyBatis基础入门《十 一》修改数据

    MyBatis基础入门<十 一>修改数据 实体类: 接口类: xml文件: 测试类: 测试结果: 数据库: 如有问题,欢迎纠正!!! 如有转载,请标明源处:https://www.cnbl ...

  8. MyBatis基础入门《十》添加数据

    MyBatis基础入门<十>添加数据 描述: 修改了实体类:TblClient.java,将其字段:cbirthday 由String类型改成了Date类型. TblClient.java ...

  9. MyBatis实现与插件开发

    分析源码之前也需要源码下载并安装到本地仓库和开发工具中,方便给代码添加注释:安装过程和mybatis源码的安装过程是一样的,这里就不再重复描述了:下载地址:https://github.com/myb ...

  10. springmvc 项目完整示例04 整合mybatis mybatis所需要的jar包 mybatis配置文件 sql语句 mybatis应用

    百度百科: MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBat ...

随机推荐

  1. python的拷贝方式以及深拷贝,浅拷贝详解

    python的拷贝方法有:切片方法, 工厂方法, 深拷贝方法, 浅拷贝方法等. 几种方法都可以实现拷贝操作, 具体区别在于两点:1.代码写法不同. 2.内存地址引用不同 代码演示: import co ...

  2. FasfDFS整合Java实现文件上传下载功能实例详解

    https://www.jb51.net/article/120675.htm 在上篇文章给大家介绍了FastDFS安装和配置整合Nginx-1.13.3的方法,大家可以点击查看下. 今天使用Java ...

  3. 【CSP-S 2019】【洛谷P5666】树的重心【主席树】【树状数组】【dfs】

    题目: 题目链接:https://www.luogu.com.cn/problem/P5666 小简单正在学习离散数学,今天的内容是图论基础,在课上他做了如下两条笔记: 一个大小为 \(n\) 的树由 ...

  4. Tensorflow细节-P80-深度神经网络

    1.本节多为复习内容,从以下图片可见一般: 2.学会使用 from numpy.random import RandomState 然后 rdm = RandomState(1) dataset_si ...

  5. 洛谷 P2827 蚯蚓 题解

    每日一题 day32 打卡 Analysis 我们可以想一下,对于每一秒除了被切的哪一个所有的蚯蚓都增长Q米,我们来维护3个队列,队列1表示最开始的蚯蚓,队列2表示每一次被切的蚯蚓被分开的较长的那一部 ...

  6. BZOJ 2159: Crash 的文明世界 第二类斯特林数+树形dp

    这个题非常巧妙啊~ #include <bits/stdc++.h> #define M 170 #define N 50003 #define mod 10007 #define LL ...

  7. codevs 5958 无

    5958 无  时间限制: 1 s  空间限制: 1000 KB  题目等级 : 大师 Master 题解       题目描述 Description 无 输入描述 Input Descriptio ...

  8. SpringData 简单的条件查询

    今天在写springdata条件查询时,JpaRepository的findOne方法,不知道是因为版本的原因还是其他原因,总是查询不出来数据 //springdata jpa版本为1.5.15,配置 ...

  9. Linux帮助指令

    1.介绍 当我们对某个指令不熟悉时,我们可以使用Linux提供的帮助指令来了解这个指令的使用方法. 2.man 指令获得帮助信息 基本语法 man  [命令或者配置文件]    (功能描述:获得帮助信 ...

  10. Promethues实战-简易教程系列

    1.监控概述 2.Promethues基础 3.Promethues初体验