插件价绍
插件是用来改变或者扩展 mybatis 的原有的功能, mybaits 的插件就是通过继承Interceptor 拦截器实现的;
MyBatis 中能使用插件进行拦截的接口和方法如下:
自定义插件
自定义慢查询插件,记录项目中执行慢的Sql
@Intercepts({
@Signature(type=StatementHandler.class,method="query",
args={Statement.class, ResultHandler.class})
// @Signature(type=StatementHandler.class,method="query",
// args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
})
public class ThresholdInterceptor implements Interceptor {
private long threshold;
@Override
public Object intercept(Invocation invocation) throws Throwable {
long begin = System.currentTimeMillis();
Object ret = invocation.proceed();
long end=System.currentTimeMillis();
long runTime = end - begin;
if(runTime>=threshold){
Object[] args = invocation.getArgs();
Statement stat = (Statement) args[0];
MetaObject metaObjectStat = SystemMetaObject.forObject(stat);
PreparedStatementLogger statementLogger =
(PreparedStatementLogger)metaObjectStat.getValue("h");
Statement statement = statementLogger.getPreparedStatement();
System.out.println("sql语句:“"+statement.toString()+"”执行时间为:"
+runTime+"毫秒,已经超过阈值!");
}
return ret;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
this.threshold = Long.valueOf(properties.getProperty("threshold"));
}
}
插件实现步骤:
(1) 实现 Interceptor 接口方法
MyBatis 插件的实现必须实现 Interceptor 接口, 该接口有如下三个方法:
public interface Interceptor {
//执行拦截逻辑的方法
Object intercept(Invocation invocation) throws Throwable;
//target是被拦截的对象,它的作用就是给被拦截的对象生成一个代理对象
Object plugin(Object target);
//读取在plugin中设置的参数
void setProperties(Properties properties);
}
(2) 确定拦截的签名
插件拦截的接口,方法及方法参数
(3) 在核心配置文件中配置插件
插件模块源码分析
插件模块的源码分析主要搞清楚初始化、 插件加载以及插件如何调用三个问题;
1)插件初始化
插件的初始化实际是在 Mybatis 第一个阶段初始化的过程中加载到 Configuration 对象中的
具体代码见: XMLConfigBuilder.pluginElement, 如下图所示:
在 configuration 对象中, 使用 interceptorChain 类属性保存所有的插件,
interceptorChain类中有个 List 用于顺序保存所有的插件;
2)插件的加载
为什么插件可以拦截 Executor、 StatementHandler、 ParameterHandler、 ResultSetHandler四个接口指定的方法呢?
那是因为通过 configuration 对象创建这四大对象时, 通过责任链模式按插件的顺序对四大对象进行了增强;
Configuration.newExecutor
Configuration.newStatementHandler
Configuration.newParameterHandler
Configuration.newResultSetHandler
3)插件的调用
插件加载是通过 ExamplePlugin.plugin(Object)来增强的,通过上面分析的interceptorChain.pluginAll方法进入会来到该方法
Plugin 方法内部一般使用Plugin.wrap(target, this)来对四大对象进行增强,
Plugin.wrap 方法代码如下:
//静态方法,用于帮助Interceptor生成动态代理
public static Object wrap(Object target, Interceptor interceptor) {
//解析Interceptor上@Intercepts注解得到的signature信息
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();//获取目标对象的类型
获取目标对象实现的接口(拦截器可以拦截4大对象实现的接口)
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
//使用jdk的方式创建动态代理
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//获取当前接口可以被拦截的方法
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
//如果当前方法需要被拦截,则调用interceptor.intercept方法进行拦截处理
return interceptor.intercept(new Invocation(target, method, args));
}
//如果当前方法不需要被拦截,则调用对象自身的方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
很明显, 归根究底 MyBatis 的插件是通过动态代理对原有的对象进行增强的
PagerHelper分页插件
使用步骤
1) 导入PageHelper的坐标
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
2)在Mybatis核心配置文件中配置插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--指定方言-->
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
3)测试分页数据
Page<TUser> startPage = PageHelper.startPage(2, 3);
List<TUser> list2 = mapper.selectByEmailAndSex2(email, sex);
System.out.println(list2.size());
文档参考:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md