插件

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
2. ParameterHandler (getParameterObject, setParameters)
3. ResultSetHandler (handleResultSets, handleOutputParameters)
4. StatementHandler (prepare, parameterize, batch, update, query)

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

mybatis插件是基于jdk的动态代理机制来实现的.

Executor是在OpenSession的时候,创建了SqlSession的时候被代理了.其它三个Handler是在执行SQL的时候在statementHandler的时候同时创建parameterHandler和ResultSetHandler这三个Handler一旦被创建就会被代理装饰.

(一)代理顺序和调用顺序

如果有多个插件的时候,代理可以被代理.



插件 - 图1

插件定义顺序是根据mybatis-config.xml代码从上往下顺序排的,

ü 代理顺序
代理顺序是 插件1先去代理被拦截对象, 然后插件2代理已经被插件1代理的对象,然后插件3去代理被插件2和插件1代理的对象.

ü 被执行的顺序

会先去调用插件3的invoke方法,然后再调用插件2的invoke方法,最后调用插件1的invoke方法.


(二)谁来创建代理对象




plugin方法会被底层的interceptChain拦截器链调用,然后对被拦截的对象进行代理,在plugin方法里面我们可以直接用Mybatis自带的Plugin.wrap方法生成代理对象(内部是jdk动态代理方式).

(三)被代理后,调用的是什么方法?


调用的是intercept方法,假如我们要去对mybatis原来的行为进行增强的话,我们就把我们实现的代码写在intercept里面,

(四)插件工作流程


当我们实现了Interceptor接口的时候,会重写plugin方法

在创建SqlSession的时候会创建Executor对象,然后会调用到InterceptorChain#pluginAll方法,在plugAll方法会循环遍历所有实现了Interceptor接口的拦截器,然后就依次调用到拦截器的plugin方法(钩子方法,具体实现逻辑是程序员自己去实现的),对我们Mybatis允许代理的接口进行代理.生成的是被代理后的Executor对象,

在Executor代理对象执行增删改查操作数据库的时候,都先会走到plugin.Plugin#invoke方法,在这个方法内部会调用到Interceptor#intercept(钩子方法,具体逻辑是程序员自己编写的,调用到的是Interceptor接口的子类的intercept方法),然后自定义一些逻辑,在走完逻辑代码之后需要调用invocation.proceed方法,就会调用到被代理对象方法(被包装的方法).

(五)API方法


在注册插件的时候可以提供很多属性,比如说plugin标签里面的property标签,然后在插件里面就可以调用到Interceptor#setProperties

(六)可拦截的方法

1.Executor


上层的对象,SQL 执行全过程,包括组装参数,组装结果集返回和执行 SQL 过程
ü
ü 可拦截的方法

update :执行 update、insert、delete 操作
query : 执行 query 操作
flushStatements : 在 commit 的时候自动调用,SimpleExecutor、ReuseExecutor、BatchExecutor 处理不同
commit : 提交事务
rollback : 事务回滚
getTransaction : 获取事务
close : 结束(关闭)事务
isClosed : 判断事务是否关闭



2.StatementHandler

执行 SQL 的过程,最常用的拦截对象


prepare : (BaseSatementHandler)SQL 预编译
parameterize : 设置参数
batch : 批处理
update : 增删改操作
query : 查询操作

3.ParameterHandler

SQL 参数组装的过程

getParameterObject : 获取参数
setParameters : 设置参数

4.ResultSetHandler


结果的组装

handleResultSets : 处理结果集
handleOutputParameters : 处理存储过程出参





(七)PageHelper实现原理


调用PageHelper.startPage的时候,调用到setLocalPage给LOCAL_PAGE(ThreadLocal)容器里面设置Page对象(Page对象里面有pageNum和pageSize)


然后在com.github.pagehelper.PageInterceptor类里面对Executor进行了包装(用了Mybatis插件),在com.github.pagehelper.PageInterceptor#intercept方法内部 通过BoundSql对象拿到要执行的SQL语句.然后根据配置的数据库方言进行SQL修改, 比如说 MySQL的话会给 limit 和LOCAL_PAGE容器里面的Page对象里面的pageNum和pageSize拿出来进行sql拼接,然后再放回去.



(八)使用场景


1. 慢查询记录,统计SQL语句执行时间,如果过长的话,记录下来交给DBA去处理.
2. 水平分表,通过方法上的注解.
3. 数据加解密.脱敏等等