前提:
以下系列文章都是基于Mybatis 版本3.4.0上开发、测试的,由于每个版本的Mybatis可能有一定的变化,因此不保证在每个Mybatis版本上都适用,请悉知。
网上已经有很多介绍Mybatis插件实现的文章了,这里我只简单的介绍一下实现一个自定义插件的主要部分,为后续的文章做准备。关于在Spring中使用Mybatis,可以查看 Mybatis-Spring
定义拦截点
Mybatis官方文档介绍到Mybatis提供了以下四个拦截点:
* Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
* ParameterHandler (getParameterObject, setParameters)
* ResultSetHandler (handleResultSets, handleOutputParameters)
* StatementHandler (prepare, parameterize, batch, update, query)
看名称就能看出来分别是针对整个执行过程的、针对参数的、针对结果集的和针对Sql语句的,在自定义插件的时候,可以根据自己的需求选择对应的拦截点。
Interceptor接口的实现
每个拦截器的实现都需要实现接口 org.apache.ibatis.plugin.Interceptor , 并使用注解 org.apache.ibatis.plugin.Intercepts 声明拦截点,从注解 org.apache.ibatis.plugin.Intercepts 的定义可以知道,一个拦截器可以同时添加多个拦截点,例如:
@Intercepts({
@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }),
@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class })
})
public class MybatisSQLExecuteMonitor implements Interceptor {
}
org.apache.ibatis.plugin.Signature 注解中的三个参数说明如下:
type
: 即上面提到的四个拦截点类
method
: 即需要拦截的类中的方法名
args
:对应方法的参数列表
方法名和方法参数列表共同决定了一个方法的签名,如果拦截点类中同一方法名存在多份,则要仔细检查参数列表,以免拦截错了。
注:
这个org.apache.ibatis.plugin.Signature会在org.apache.ibatis.plugin.Interceptor的实现中处处涉及到,所以了解其中的各个部分很重要。
接口方法的使用及说明
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
看org.apache.ibatis.plugin.Interceptor接口的定义,只有三个方法:
setProperties(Properties properties)
用来在声明拦截器的时候,传入一些自定义的属于,这个是通过mybatis的配置文件声明的,如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<plugins>
<plugin interceptor="com.xxx.xxx.MybatisSQLExecuteMonitor" >
<property name="name" value="value"/>
</plugin>
</plugins>
</configuration>
plugin(Object target)
用于传入被拦截的类对象,其中target
即为被拦截的类对象,也即当前被拦截的Signature
中的type
对像,可以通过下面的调用关系链进到源码里去验证这一点:
通常可以简单实现如下,除非你想自定义代理实现:
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
Plugin.wrap(target, this)
中是通过动态代理实现的(我稍后有一篇专门介绍动态代理的文章),源码如下:
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
intercept(Invocation invocation)
此方法为拦截后的具体逻辑实现。方法参数invocation
中只有三个变量,分别对应org.apache.ibatis.plugin.Signature声明中的三个部分,以下面代码为例:
@Intercepts({
@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }),
@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class })
})
public class MybatisSQLExecuteMonitor implements Interceptor {
}
target
: 拦截到的类对象,即 org.apache.ibatis.executor.Executor 类对象
method
: 拦截到的方法,即 query或update
args
:拦截到的方法参数列表,
- 如果
method
是query,则args
为:
MappedStatement.class
Object.class
RowBounds.class
ResultHandler.class
,CacheKey.class
,BoundSql.class
- 如果
method
是update,则args
为:
MappedStatement.class
Object.class
注:
org.apache.ibatis.executor.Executor中所有的delete/insert/update的sql操作对应的方法名都是update
,所以如果想区分具体的sql动作,需要根据args[0]
(即一个MappedStatement对象)中的SqlCommandType来判断。
以上即是Mybatis中实现一个拦截器所需要了解的基础知识,下一篇中,我将介绍如何实现一个简单的Sql执行日志,日志内容包含:
操作人、传入的参数、返回的结果条数、所花费的时间、sql的id、sql的原始语句