第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

MyBatis 插件

1. 前言

MyBatis 允許我們以插件的形式對(duì)已映射語(yǔ)句執(zhí)行過(guò)程中的某一點(diǎn)進(jìn)行攔截調(diào)用,通俗一點(diǎn)來(lái)說(shuō),MyBatis 的插件其實(shí)更應(yīng)該被稱作為攔截器。

MyBatis 插件的使用十分廣泛,分頁(yè)、性能分析、樂(lè)觀鎖、邏輯刪除等等常用的功能都可以通過(guò)插件來(lái)實(shí)現(xiàn)。既然插件如此好用,本小節(jié)我們就一起來(lái)探索插件并且實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 SQL 執(zhí)行時(shí)間計(jì)時(shí)插件。

2. 介紹

2.1 可攔截對(duì)象

MyBatis 允許插件攔截如下 4 個(gè)對(duì)象的方法。

  • Executor的 update, query, flushStatements, commit, rollback, getTransaction, close, isClosed 方法
  • ParameterHandler的 getParameterObject, setParameters 方法
  • ResultSetHandler的 handleResultSets, handleOutputParameters 方法
  • StatementHandler的 prepare, parameterize, batch, update, query 方法

注意,這四個(gè)對(duì)象都是接口,插件會(huì)攔截實(shí)現(xiàn)了該接口的對(duì)象。

2.2 插件接口

插件必須實(shí)現(xiàn) Interceptor 接口。Interceptor 接口共有 3 個(gè)方法,如下:

public interface Interceptor {
    Object intercept(Invocation invocation) throws Throwable;

    default Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    default void setProperties(Properties properties) {
    }
}

其中 plugin 和 setProperties 方法都是默認(rèn)實(shí)現(xiàn)的方法,我們可以選擇不覆蓋實(shí)現(xiàn),而 intercept 方法則必須實(shí)現(xiàn)。如下:

  • intercept : 核心方法,通過(guò) Invocation 我們可以拿到被攔截的對(duì)象,從而實(shí)現(xiàn)自己的邏輯。
  • plugin: 給 target 攔截對(duì)象生成一個(gè)代理對(duì)象,已有默認(rèn)實(shí)現(xiàn)。
  • setProperties: 插件的配置方法,在插件初始化的時(shí)候調(diào)用。

2.3 攔截器簽名

插件可對(duì)多種對(duì)象進(jìn)行攔截,因此我們需要通過(guò)攔截器簽名來(lái)告訴 MyBatis 插件應(yīng)該攔截何種對(duì)象的何種方法。舉例如下:

@Intercepts({@Signature(
  type = StatementHandler.class,
  method = "prepare",
  args = {Connection.class, Integer.class}
)})
public class XXXPlugin implements Interceptor {}

類 XXXPlugin 上有兩個(gè)注解:

  • Intercepts注解: 攔截聲明,只有 Intercepts 注解修飾的插件才具有攔截功能。
  • Signature注解: 簽名注解,共 3 個(gè)參數(shù),type 參數(shù)表示攔截的對(duì)象,如 StatementHandler,另外還有Executor、ParameterHandler和ResultSetHandler;method 參數(shù)表示攔截對(duì)象的方法名,即對(duì)攔截對(duì)象的某個(gè)方法進(jìn)行攔截,如 prepare,代表攔截 StatementHandler 的 prepare 方法;args 參數(shù)表示攔截方法的參數(shù),因?yàn)榉椒赡軙?huì)存在重載,因此方法名加上參數(shù)才能唯一標(biāo)識(shí)一個(gè)方法。

推斷可知 XXXPlugin 插件會(huì)攔截 StatementHandler對(duì)象的 prepare(Connection connection, Integer var2) 方法。

一個(gè)插件可以攔截多個(gè)對(duì)象的多個(gè)方法,因此在 Intercepts 注解中可以添加上多個(gè) Signature注解。

3. 實(shí)踐

接下來(lái),我們一起來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 SQL 執(zhí)行時(shí)間計(jì)時(shí)插件。插件的功能是日志輸出每一條 SQL 的執(zhí)行用時(shí)。

在 com.imooc.mybatis 包下,我們新建 plugin 包,并在包中添加 SqlStaticsPlugin 類。SqlStaticsPlugin 會(huì)攔截 StatementHandler的prepare方法,如下:

package com.imooc.mybatis.plugin;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;

@Intercepts({@Signature(
  type = StatementHandler.class,
  method = "prepare",
  args = {Connection.class, Integer.class}
)})
public class SqlStaticsPlugin implements Interceptor {
  private Logger logger = LoggerFactory.getLogger(SqlStaticsPlugin.class);

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }
}

我們一起來(lái)完善這個(gè)插件。

  1. 首先需要得到 invocation 的攔截對(duì)象 StatementHandler,并從 StatementHandler 中拿到 SQL 語(yǔ)句。
  2. 得到當(dāng)前的時(shí)間戳 startTime。
  3. 執(zhí)行 SQL。
  4. 得到執(zhí)行后的時(shí)間戳 endTime。
  5. 計(jì)算時(shí)間差,并打印 SQL 耗時(shí)。

對(duì)應(yīng)的 intercept 方法代碼如下:

public Object intercept(Invocation invocation) throws Throwable {
  // 得到攔截對(duì)象
  StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
  MetaObject metaObj = SystemMetaObject.forObject(statementHandler);
  String sql = (String) metaObj.getValue("delegate.boundSql.sql");
  // 開(kāi)始時(shí)間
  long startTime = System.currentTimeMillis();
  // 執(zhí)行SQL
  Object res = invocation.proceed();
  // 結(jié)束時(shí)間
  long endTime = System.currentTimeMillis();
  long sqlCost = endTime - startTime;
  // 去掉無(wú)用的換行符,打印美觀
  logger.info("sql: {} - cost: {}ms", sql.replace("\n", ""), sqlCost);
  // 返回執(zhí)行的結(jié)果
  return res;
}

注意,通過(guò)反射調(diào)用后的結(jié)果 res,我們一定要記得返回。MyBatis 提供了 MetaObject 這個(gè)類來(lái)方便我們進(jìn)行攔截對(duì)象屬性的修改,這里我們簡(jiǎn)單的使用了getValue方法來(lái)得到 SQL 語(yǔ)句。

我們?cè)谌峙渲梦募?cè)這個(gè)插件:

<plugins>
  <plugin interceptor="com.imooc.mybatis.plugin.SqlStaticsPlugin" />
</plugins>

到這,這個(gè)插件已經(jīng)可以工作了,但是我們希望它能更加靈活一點(diǎn),通過(guò)配置來(lái)攔截某些類型的 SQL,如只計(jì)算 select 類型SQL的耗時(shí)。

插件會(huì)在初始化的時(shí)候通過(guò) setProperties 方法來(lái)加載配置,利用它我們可以得到哪些方法需要被計(jì)時(shí)。如下:

public class SqlStaticsPlugin implements Interceptor {
  private List<String> methods = Arrays.asList("SELECT", "INSERT", "UPDATE", "DELETE");

  @Override
  public void setProperties(Properties properties) {
    String methodsStr = properties.getProperty("methods");
    if (methodsStr == null || methodsStr.isBlank())
      return;
    String[] parts = methodsStr.split(",");
    methods = Arrays.stream(parts).map(String::toUpperCase).collect(Collectors.toList());
  }
}

methods 參數(shù)默認(rèn)可通過(guò) select、insert、update 和 delete 類型的SQL語(yǔ)句,如果插件存在配置項(xiàng) methods,那么則根據(jù)插件配置來(lái)覆蓋默認(rèn)配置。

在全局配置文件中,我們來(lái)添加上 methods 這個(gè)配置:

<plugins>
  <plugin interceptor="com.imooc.mybatis.plugin.SqlStaticsPlugin">
    <property name="methods" value="select,update"/>
  </plugin>
</plugins>

類型之間以 , 隔開(kāi),MyBatis 會(huì)在插件初始化時(shí),自動(dòng)將 methods 對(duì)應(yīng)的值通過(guò) setProperties 方法來(lái)傳遞給SqlStaticsPlugin插件。插件拿到 Properties 后解析并替換默認(rèn)的 methods 配置。

再次完善一下 intercept 方法,使其支持配置攔截:

public Object intercept(Invocation invocation) throws Throwable {
  StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
  MetaObject metaObj = SystemMetaObject.forObject(statementHandler);
  // 得到SQL類型
  String sqlCommandType = metaObj.getValue("delegate.mappedStatement.sqlCommandType").toString();
  // 如果方法配置中沒(méi)有SQL類型,則無(wú)需計(jì)時(shí),直接返回調(diào)用
  if (!methods.contains(sqlCommandType)) {
    return invocation.proceed();
  }
  String sql = (String) metaObj.getValue("delegate.boundSql.sql");
  long startTime = System.currentTimeMillis();
  Object res = invocation.proceed();
  long endTime = System.currentTimeMillis();
  long sqlCost = endTime - startTime;
  logger.info("sql: {} - cost: {}ms", sql.replace("\n", ""), sqlCost);
  return res;
}

當(dāng)插件注冊(cè)后,應(yīng)用程序會(huì)打印出如下的日志語(yǔ)句:

17:48:14.110 [main] INFO com.imooc.mybatis.plugin.SqlStaticsPlugin - sql: INSERT INTO blog(info,tags)    VALUES(?,    ?) - cost: 87ms

至此,一個(gè)簡(jiǎn)單的 SQL 計(jì)時(shí)插件就開(kāi)發(fā)完畢了。

4. 小結(jié)

  • MyBatis 插件強(qiáng)大且易用,是深入掌握 MyBatis 的必備知識(shí)點(diǎn)。
  • 不少 MyBatis 三方庫(kù)都提供了很多好用的插件,如 Pagehelper 分頁(yè)插件,我們可以拿來(lái)即用。