前言
SQL性能监控是一个步伐必要的功能,通常我们可以利用数据库自带的客户端工具进行SQL性能分析。然而对于一些专业度不高的人员来说,当步伐出现卡顿或者相应速度变慢时,排查标题变得困难。当步伐出现卡顿,通常通过检查服务器磁盘利用情况、步伐内存巨细,网络带宽以及数据库I/O等方面进行标题排查。然而数据库I/O打高的情况通常是由于SQL实行服从过低导致的。一般项目制的公司都有属于本身的实施人员,然而要让实施人员去排查具体SQL实行过慢标题,这显然对于专业度不高的工作人员来说是一种挑战和煎熬。因此本系列文章将介绍怎样利用Mybatis的拦截器功能完成对SQL实行的时间记载,并通过MQ推送至SQL记载服务,记载具体的慢SQL信息,后续可以通过页面进行展示。通过可视化的方式让实施人员快速定位到标题所在。
一、根本功能介绍
本章节只实现Mybatis实行时对实行SQL进行拦截,控制台打印实行SQL包罗参数、实行方法以及实行时间。大抵结构图如下:
对慢SQL进行发送MQ,记载表现到前端界面的功能,将在本系列文章第二章实现。
1.1本章功能效果预览图:
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()[0] instanceof MappedStatement)) {
- return returnValue;
- }
- // just handle mappedStatement
- MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
- // get BoundSql
- BoundSql boundSql = null;
- for (int i = invocation.getArgs().length - 1; i >= 0; i--) {
- if (invocation.getArgs()[i] instanceof BoundSql) {
- boundSql = (BoundSql) invocation.getArgs()[i];
- 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()[1];
- }
- 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企服之家,中国第一个企服评测及商务社交产业平台。 |