你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

mybatis自定义插件开发

2021/12/2 23:55:00

现在有这样一个需求:
需要自定义mybatis插件判断当前sql执行时间时候超过自定义的值,如果超时就打印相应的日志

  1. 插件自定义代码:
    github代码地址
@Intercepts(
        {
                @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
                @Signature(type = Executor.class, method = "queryCursor", args = {MappedStatement.class, Object.class, RowBounds.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}),
        }
)
public class ConsumeTimeInterceptor implements Interceptor {

  public static Log log = LogFactory.getLog(ConsumeTimeInterceptor.class);

  long limitMilliSecond;

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    long start = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
    try {
      return invocation.proceed();
    } finally {
      try {
        long end = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        String methodName = invocation.getMethod().getName();
        long consumeTime = end - start;
        if (consumeTime > limitMilliSecond) {
          String logString;
          if ("update".equalsIgnoreCase(methodName)) {
            logString = "slow update consume milliSecond: ";
          } else {
            logString = "slow query consume milliSecond: ";
          }
          log.debug(logString + consumeTime);
        }
      } catch (Exception e) {
        log.error(e.getMessage());
      }
    }
  }

  @Override
  public void setProperties(Properties properties) {
    String limitMilliSecond = properties.getProperty("limitMilliSecond");
    if (limitMilliSecond != null) {
      this.limitMilliSecond = Long.parseLong(limitMilliSecond);
    }
  }
}

通过实现Interceptor接口,就可以实现一个自定义插件

  1. 在mybatis配置文件配置插件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <plugins>
        <plugin interceptor="io.github.mybatis.pal.ConsumeTimeInterceptor">
            <property name="limitMilliSecond" value="10"/>
        </plugin>
    </plugins>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">
                <property name="" value="" />
            </transactionManager>
            <dataSource type="UNPOOLED">
                <property name="driver" value="org.hsqldb.jdbcDriver" />
                <property name="url" value="jdbc:hsqldb:mem:basetest" />
                <property name="username" value="sa" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper class="io.github.mybatis.pal.Mapper" />
    </mappers>

</configuration>

  1. 我们看一下源码,看看插件是如何被设置生效的
    DefaultSqlSessionFactory中的方法
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 调用configuration的方法生成executor对象
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

configuration.newExecutor()方法如下:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor 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 (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 生成executor执行interceptorChain的方法是所有插件生效
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

interceptorChain生成:在项目启动,configuration被初始化,读取配置文件生成

这里我们看到作用于executor的并不是单个插件,是一个插件链,意味着我们可以定义多个插件,也可能我们引入的其他插件也会作用于executor

比如:如果我们实际项目中引入了分页插件,此时分页插件也会在此处作用于executor,从而达到分页的目的

这里设置插件使用了责任链模式,返回的是一个动态代理类,使用了代理模式,具体可以看mybatis源码

如果想要自己实现一个插件,可以上github下载代码,依葫芦画瓢