Mybatis自定义插件
相信工作中用mybatis的同学大部分都使用过PageHelper分布插件,最近也是想了解一下PageHelper的实现原理,PageHelper也是通过mybatis的插件来实现的。具体怎么去实现一个mybatis插件下面做具体的介绍。
创新互联长期为数千家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为蒙山企业提供专业的成都网站制作、网站建设、外贸网站建设,蒙山网站改版等技术服务。拥有十多年丰富建站经验和众多成功案例,为您定制开发。
1.mybatis插件机制
工作中遇到过一个场景,打印mybatis的执行sql日志到公司日志平台。那么就需要自定义mybatis插件来实现,在执行sql之前,希望能够拦截到mybatis的执行sql,然后使用公司的日志框架打印日志。
myba支持拦截的方法:
- ParameterHandler:处理SQL的参数对象;
- ResultSetHandler:处理SQL的返回结果集;
- StatementHandler:执行SQL语句;
- Executor:执行器,执行增删改查操作;
在自定义mybatis插件的时候,需要指定自己所需要的拦截方式,例如我上面工作中是需要使用StatementHandler。
2.mybatis插件使用到的设计模式
mybatis插件中主要使用到了两种设计模式:动态代理和责任链模式;
1)责任链模式
责任链模式
在说mybatis中的责任链之前,我们先回想一下责任链模式:
责任链模式中涉及两个角色:
- 抽象处理者(Handler)角色:定义处理请求的接口或者抽象类,提供处理请求的方法和设置下一个处理者的方法。
具体处理者角色(ConcretaHandler)角色:处理具体的请求或者将请求发送到下一个具体处理者
具体处理者拥有下一下处理者的引用,如果需要下一个处理者处理,那么调用下一个处理者的处理方法即可。
mybatis插件中责任链模式的应用
mybatis中的插件实际上也可以叫做拦截器,mybatis中使用责任链模式将臃肿的功能拆分成单一的Handler处理类中,开发人员可以根据业务需求将多个Handler对象组合成一条责任链,实现请求的处理。mybatis也是通过这种模式来提供mybatis的扩展性。例如:现在有 HandlerA、HandlerB、HandlerC三个字段的业务逻辑
当业务只需要HandlerA和HandlerC时,只需要动态组合得到HandlerA->HandlerC就可以了。
2)动态代理
mybatis中的Executor、ParameterHandler、ResultSetHandler、StatementHandler它们都是通过Configuration.new()方法来创建的,而Configuration.new()方法实际上调用的是InterceptorChain.pluginAll()方法来生成代理对象,所以通过Configuration.new*()系列方法得到的对象实际是一个代理对象。
以Configuration.newExecutor()方法为例介绍:
1.Configuration.newExecutor()
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
2.interceptorChain.pluginAll(executor)
public Object pluginAll(Object target) {
Interceptor interceptor;
for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
interceptor = (Interceptor)var2.next();
}
return target;
}
3.target = interceptor.plugin(target)
interceptor.plugin()方法实际上是我们自定义插件的plugin方法。
一般我们这个方法的实现是通过Plugin.wrap来生成代理
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
4. Plugin.wrap
public static Object wrap(Object target, Interceptor interceptor) {
Map, Set> signatureMap = getSignatureMap(interceptor);
Class> type = target.getClass();
Class>[] interfaces = getAllInterfaces(type, signatureMap);
return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
}
3.实现自定义Mybatis插件
mybatis中使用的拦截器都需要实现Interceptor接口
public interface Interceptor {
Object intercept(Invocation var1) throws Throwable;
Object plugin(Object var1);
void setProperties(Properties var1);
}
用户自定义的拦截器除了继承Interceptor接口,还需要使用@Intercepts和@Signature两个注解标识。
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
}),
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,
CacheKey.class, BoundSql.class
})})
例如上面的定义:
- type:属性指定需要拦截的类型
- method:属性指定需要拦截的方法
- args:属性指定了被拦截方法的参数列表
将我们自定义的mybatis的插件配置到mybatis-config.xml中
4.案例
例如,我们比较常用的对sql进行监控,监控sql的执行时长及慢查询等,那么我们就可以通过编写MonitorInterceptor拦截器
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
}),
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,
CacheKey.class, BoundSql.class
})})
@Slf4j
public class MonitorInercepor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
long start = System.currentTimeMillis();
Object result = invocation.proceed(); // 获取代理
long end = System.currentTimeMillis();
long cost = end - start;
log.debug("[TimerInterceptor] execute [{}] cost [{}] ms, parameter:{}", ms.getId(), cost, parameter);
if (cost > 1000) {
log.warn("Sql语句执行时间超过1秒钟,请检查优化,方法:{},耗时:{}ms,参数:{}", ms.getId(), cost, parameter);
}
return result;
} catch (Throwable r) {
log.error(r.getMessage(), r);
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) { }
}
文章标题:Mybatis自定义插件
分享路径:http://myzitong.com/article/gsjsii.html