相信工作中用mybatis的同學(xué)大部分都使用過PageHelper分布插件,最近也是想了解一下PageHelper的實(shí)現(xiàn)原理,PageHelper也是通過mybatis的插件來實(shí)現(xiàn)的。具體怎么去實(shí)現(xiàn)一個(gè)mybatis插件下面做具體的介紹。
創(chuàng)新互聯(lián)公司堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站制作、成都網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的吉水網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!工作中遇到過一個(gè)場景,打印mybatis的執(zhí)行sql日志到公司日志平臺(tái)。那么就需要自定義mybatis插件來實(shí)現(xiàn),在執(zhí)行sql之前,希望能夠攔截到mybatis的執(zhí)行sql,然后使用公司的日志框架打印日志。
myba支持?jǐn)r截的方法:
在自定義mybatis插件的時(shí)候,需要指定自己所需要的攔截方式,例如我上面工作中是需要使用StatementHandler。
mybatis插件中主要使用到了兩種設(shè)計(jì)模式:動(dòng)態(tài)代理和責(zé)任鏈模式;
責(zé)任鏈模式
在說mybatis中的責(zé)任鏈之前,我們先回想一下責(zé)任鏈模式:
責(zé)任鏈模式中涉及兩個(gè)角色:
具體處理者角色(ConcretaHandler)角色:處理具體的請求或者將請求發(fā)送到下一個(gè)具體處理者
具體處理者擁有下一下處理者的引用,如果需要下一個(gè)處理者處理,那么調(diào)用下一個(gè)處理者的處理方法即可。
mybatis插件中責(zé)任鏈模式的應(yīng)用
mybatis中的插件實(shí)際上也可以叫做攔截器,mybatis中使用責(zé)任鏈模式將臃腫的功能拆分成單一的Handler處理類中,開發(fā)人員可以根據(jù)業(yè)務(wù)需求將多個(gè)Handler對(duì)象組合成一條責(zé)任鏈,實(shí)現(xiàn)請求的處理。mybatis也是通過這種模式來提供mybatis的擴(kuò)展性。
例如:現(xiàn)在有 HandlerA、HandlerB、HandlerC三個(gè)字段的業(yè)務(wù)邏輯
當(dāng)業(yè)務(wù)只需要HandlerA和HandlerC時(shí),只需要?jiǎng)討B(tài)組合得到HandlerA->HandlerC就可以了。
mybatis中的Executor、ParameterHandler、ResultSetHandler、StatementHandler它們都是通過Configuration.new()方法來創(chuàng)建的,而Configuration.new()方法實(shí)際上調(diào)用的是InterceptorChain.pluginAll()方法來生成代理對(duì)象,所以通過Configuration.new*()系列方法得到的對(duì)象實(shí)際是一個(gè)代理對(duì)象。
以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()方法實(shí)際上是我們自定義插件的plugin方法。
一般我們這個(gè)方法的實(shí)現(xiàn)是通過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;
}
mybatis中使用的攔截器都需要實(shí)現(xiàn)Interceptor接口
public interface Interceptor {
Object intercept(Invocation var1) throws Throwable;
Object plugin(Object var1);
void setProperties(Properties var1);
}
用戶自定義的攔截器除了繼承Interceptor接口,還需要使用@Intercepts和@Signature兩個(gè)注解標(biāo)識(shí)。
@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
})})
例如上面的定義:
將我們自定義的mybatis的插件配置到mybatis-config.xml中
例如,我們比較常用的對(duì)sql進(jìn)行監(jiān)控,監(jiān)控sql的執(zhí)行時(shí)長及慢查詢等,那么我們就可以通過編寫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語句執(zhí)行時(shí)間超過1秒鐘,請檢查優(yōu)化,方法:{},耗時(shí):{}ms,參數(shù):{}", 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) { }
}
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。