小秦哥 发表于 2024-12-31 20:20:17

Mybatis控制台打印SQL实行信息(实行方法、实行SQL、实行时间)

前言

SQL性能监控是一个步伐必要的功能,通常我们可以利用数据库自带的客户端工具进行SQL性能分析。然而对于一些专业度不高的人员来说,当步伐出现卡顿或者相应速度变慢时,排查标题变得困难。当步伐出现卡顿,通常通过检查服务器磁盘利用情况、步伐内存巨细,网络带宽以及数据库I/O等方面进行标题排查。然而数据库I/O打高的情况通常是由于SQL实行服从过低导致的。一般项目制的公司都有属于本身的实施人员,然而要让实施人员去排查具体SQL实行过慢标题,这显然对于专业度不高的工作人员来说是一种挑战和煎熬。因此本系列文章将介绍怎样利用Mybatis的拦截器功能完成对SQL实行的时间记载,并通过MQ推送至SQL记载服务,记载具体的慢SQL信息,后续可以通过页面进行展示。通过可视化的方式让实施人员快速定位到标题所在。
https://i-blog.csdnimg.cn/direct/a9aeba66411244138c667ed322f32741.png
一、根本功能介绍

本章节只实现Mybatis实行时对实行SQL进行拦截,控制台打印实行SQL包罗参数、实行方法以及实行时间。大抵结构图如下:
https://i-blog.csdnimg.cn/direct/a92ca87485db4b3fac4993d82d06209d.png
对慢SQL进行发送MQ,记载表现到前端界面的功能,将在本系列文章第二章实现。
1.1本章功能效果预览图:

https://i-blog.csdnimg.cn/direct/86f3798e1b404901987f74f2f7034891.png
   Mapper Method: 表现该SQL是由哪个Mapper方法进行调用实行。
Execute SQL:打印出完整实行的SQL,自动添补了参数。
Spend Time:记载本次SQL实行耗费的时间。
二、可实行源码

2.1 yaml底子设置

必要在yaml设置文件中设置是否打印SQL实行信息。当然该设置可以放入Redis中,以方便后续面向微服务时,可以一键开启和关闭,这里就不再演示,后续扩展可有您自主实现。
mybatis-analyze:
show-log: true #SQL打印到控制台
2.2 MybatisAnalyzeSQLInterceptor实现SQL拦截

源码可直接复制运行!!!!!
package com.hl.by.common.mybatis.interceptor;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
* @Author: DI.YIN
* @Date: 2024/11/25 16:32
* @Version: 1.0.0
* @Description: Mybatis SQL分析插件
**/
@Slf4j
@Intercepts(value = {
      @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
      @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}),
})
@Component
public class MybatisAnalyzeSQLInterceptor implements Interceptor {

    @Value("${mybatis-analyze.show-log:false}")
    private Boolean showLog;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

      StopWatch startedWatch = StopWatch.createStarted();
      Object returnValue = null;
      Exception proceedSQLException = null;
      try {
            returnValue = invocation.proceed();
      } catch (Exception e) {
            proceedSQLException = e;
      }
      startedWatch.stop();
      long spendTime = startedWatch.getTime(TimeUnit.MILLISECONDS);
      if (invocation.getArgs() == null || !(invocation.getArgs() instanceof MappedStatement)) {
            return returnValue;
      }
      // just handle mappedStatement
      MappedStatement mappedStatement = (MappedStatement) invocation.getArgs();
      // get BoundSql
      BoundSql boundSql = null;
      for (int i = invocation.getArgs().length - 1; i >= 0; i--) {
            if (invocation.getArgs() instanceof BoundSql) {
                boundSql = (BoundSql) invocation.getArgs();
                break;
            }
      }
      if (invocation.getTarget() instanceof RoutingStatementHandler) {
            RoutingStatementHandler routingStatementHandler = (RoutingStatementHandler) invocation.getTarget();
            boundSql = routingStatementHandler.getBoundSql();
      }
      if (boundSql == null) {
            Object parameter = null;
            if (invocation.getArgs().length > 1) {
                parameter = invocation.getArgs();
            }
            boundSql = mappedStatement.getBoundSql(parameter);
      }
      //
      printProcessedSQL(boundSql, mappedStatement.getConfiguration(), mappedStatement.getId(), spendTime);
      // If an exception occurs during SQL execution,throw exception
      if (proceedSQLException != null) {
            throw proceedSQLException;
      }
      return returnValue;
    }

    /**
   * Parse SQL and Print SQL
   *
   * @param boundSql
   * @param configuration
   * @param statement
   * @param spendTime
   */
    private void printProcessedSQL(BoundSql boundSql, Configuration configuration, String statement, long spendTime) {
      Map<Integer, Object> parameterValueMap = parseParameterValues(configuration, boundSql);
      String finalSQL = fillSqlParams(boundSql.getSql(), parameterValueMap);
      finalSQL = finalSQL.replaceAll("\n", "");
      String printData = "\n===============Start Print SQL===============\n" +
                "Mapper Method: [ " + statement + " ]\n" +
                "Execute SQL: " + finalSQL + " \n" +
                "Spend Time: " + spendTime + " ms \n" +
                "===============End Print SQL===============\n";
      if (showLog) {
            log.info(printData);
      }
    }

    public static String fillSqlParams(String statementQuery, Map<Integer, Object> parameterValues) {
      final StringBuilder sb = new StringBuilder();
      int currentParameter = 0;
      for (int pos = 0; pos < statementQuery.length(); pos++) {
            char character = statementQuery.charAt(pos);
            if (statementQuery.charAt(pos) == '?' && currentParameter <= parameterValues.size()) {
                Object value = parameterValues.get(currentParameter);
                sb.append(value != null ? value.toString() : new MybatisAnalyzeSQLInterceptor.Values().toString());
                currentParameter++;
            } else {
                sb.append(character);
            }
      }
      return sb.toString();
    }

    /**
   * 用于解析参数值
   *
   * @param configuration
   * @param boundSql
   * @return Map<Integer, Object>
   */
    private static Map<Integer, Object> parseParameterValues(Configuration configuration, BoundSql boundSql) {
      Object parameterObject = boundSql.getParameterObject();
      List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
      if (parameterMappings != null) {
            Map<Integer, Object> parameterValues = new HashMap<>();
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                  Object value;
                  String propertyName = parameterMapping.getProperty();
                  if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                  } else if (parameterObject == null) {
                        value = null;
                  } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                  } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                  }
                  parameterValues.put(i, new MybatisAnalyzeSQLInterceptor.Values(value));
                }
            }
            return parameterValues;
      }
      return Collections.emptyMap();
    }

    @Override
    public Object plugin(Object target) {
      return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties0) {
    }

    @Setter
    @Getter
    public static class Values {
      public static final String NORM_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";

      public static final String databaseDialectDateFormat = NORM_DATETIME_PATTERN;
      public static final String databaseDialectTimestampFormat = NORM_DATETIME_PATTERN;
      private Object value;

      public Values(Object valueToSet) {
            this();
            this.value = valueToSet;
      }

      public Values() {
      }

      @Override
      public String toString() {
            return convertToString(this.value);
      }

      public String convertToString(Object value) {
            String result;

            if (value == null) {
                result = "NULL";
            } else {
                if (value instanceof byte[]) {
                  result = new String((byte[]) value);
                } else if (value instanceof Timestamp) {
                  result = new SimpleDateFormat(databaseDialectTimestampFormat).format(value);
                } else if (value instanceof Date) {
                  result = new SimpleDateFormat(databaseDialectDateFormat).format(value);
                } else if (value instanceof Boolean) {
                  result = Boolean.FALSE.equals(value) ? "0" : "1";
                } else {
                  result = value.toString();
                }
                result = quoteIfNeeded(result, value);
            }

            return result;
      }

      private String quoteIfNeeded(String stringValue, Object obj) {
            if (stringValue == null) {
                return null;
            }
            if (Number.class.isAssignableFrom(obj.getClass()) || Boolean.class.isAssignableFrom(obj.getClass())) {
                return stringValue;
            } else {
                return "'" + escape(stringValue) + "'";
            }
      }

      private String escape(String stringValue) {
            return stringValue.replaceAll("'", "''");
      }
    }
}

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Mybatis控制台打印SQL实行信息(实行方法、实行SQL、实行时间)