Mybatis自定義插件

相信工做中用mybatis的同窗大部分都使用過PageHelper分佈插件,最近也是想了解一下PageHelper的實現原理,PageHelper也是經過mybatis的插件來實現的。具體怎麼去實現一個mybatis插件下面作具體的介紹。sql

1.mybatis插件機制

工做中遇到過一個場景,打印mybatis的執行sql日誌到公司日誌平臺。那麼就須要自定義mybatis插件來實現,在執行sql以前,但願可以攔截到mybatis的執行sql,而後使用公司的日誌框架打印日誌。
myba支持攔截的方法:設計模式

  • ParameterHandler:處理SQL的參數對象;
  • ResultSetHandler:處理SQL的返回結果集;
  • StatementHandler:執行SQL語句;
  • Executor:執行器,執行增刪改查操做;

在自定義mybatis插件的時候,須要指定本身所須要的攔截方式,例如我上面工做中是須要使用StatementHandler。mybatis

2.mybatis插件使用到的設計模式

mybatis插件中主要使用到了兩種設計模式:動態代理和責任鏈模式;app

1)責任鏈模式

責任鏈模式
在說mybatis中的責任鏈以前,咱們先回想一下責任鏈模式:
Mybatis自定義插件
責任鏈模式中涉及兩個角色:框架

  • 抽象處理者(Handler)角色:定義處理請求的接口或者抽象類,提供處理請求的方法和設置下一個處理者的方法。
  • 具體處理者角色(ConcretaHandler)角色:處理具體的請求或者將請求發送到下一個具體處理者
    具體處理者擁有下一下處理者的引用,若是須要下一個處理者處理,那麼調用下一個處理者的處理方法便可。
    mybatis插件中責任鏈模式的應用
    mybatis中的插件實際上也能夠叫作攔截器,mybatis中使用責任鏈模式將臃腫的功能拆分紅單一的Handler處理類中,開發人員能夠根據業務需求將多個Handler對象組合成一條責任鏈,實現請求的處理。mybatis也是經過這種模式來提供mybatis的擴展性。ide

    例如:如今有 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()this

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) debug

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<Class<?>, Set<Method>> 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中

<plugins>
            <plugin interceptor="com.xxx.XXXInterceptor">
                    <!-- 對攔截器中的屬性進行初始化 -->
                    <property name="name" value="Bob"/>
            </plugin>
    </plugins>

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) { }
}
相關文章
相關標籤/搜索