Mybatis控制臺打印SQL執(zhí)行信息的方法詳解
前言
SQL性能監(jiān)控是一個程序必要的功能,通常我們可以使用數(shù)據(jù)庫自帶的客戶端工具進行SQL性能分析。然而對于一些專業(yè)度不高的人員來說,當程序出現(xiàn)卡頓或者響應速度變慢時,排查問題變得困難。當程序出現(xiàn)卡頓,通常通過檢查服務器磁盤使用情況、程序內存大小,網(wǎng)絡帶寬以及數(shù)據(jù)庫I/O等方面進行問題排查。然而數(shù)據(jù)庫I/O打高的情況通常是由于SQL執(zhí)行效率過低導致的。一般項目制的公司都有屬于自己的實施人員,然而要讓實施人員去排查具體SQL執(zhí)行過慢問題,這顯然對于專業(yè)度不高的工作人員來說是一種挑戰(zhàn)和煎熬。因此本系列文章將介紹如何使用Mybatis的攔截器功能完成對SQL執(zhí)行的時間記錄,并通過MQ推送至SQL記錄服務,記錄具體的慢SQL信息,后續(xù)可以通過頁面進行展示。通過可視化的方式讓實施人員快速定位到問題所在。
一、基本功能介紹
本章節(jié)只實現(xiàn)Mybatis執(zhí)行時對執(zhí)行SQL進行攔截,控制臺打印執(zhí)行SQL包括參數(shù)、執(zhí)行方法以及執(zhí)行時間。大致結構圖如下:
1.1本章功能效果預覽圖:
Mapper Method: 顯示該SQL是由哪個Mapper方法進行調用執(zhí)行。
Execute SQL:打印出完整執(zhí)行的SQL,自動填充了參數(shù)。
Spend Time:記錄本次SQL執(zhí)行花費的時間。
二、可執(zhí)行源碼
2.1 yaml基礎配置
需要在yaml配置文件中配置是否打印SQL執(zhí)行信息。當然該配置可以放入Redis中,以方便后續(xù)面向微服務時,可以一鍵開啟和關閉,這里就不再演示,后續(xù)擴展可有您自主實現(xiàn)。
mybatis-analyze: show-log: true #SQL打印到控制臺
2.2 MybatisAnalyzeSQLInterceptor實現(xiàn)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(); } /** * 用于解析參數(shù)值 * * @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("'", "''"); } } }
到此這篇關于Mybatis控制臺打印SQL執(zhí)行信息的方法詳解的文章就介紹到這了,更多相關Mybatis控制臺打印SQL內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
詳解Spring boot Admin 使用eureka監(jiān)控服務
本篇文章主要介紹了詳解Spring boot Admin 使用eureka監(jiān)控服務,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12Springboot中使用Redis實現(xiàn)分布式鎖的示例代碼
在分布式系統(tǒng)中,為了保證數(shù)據(jù)的一致性和任務的互斥執(zhí)行,分布式鎖是一種常見的解決方案,本文主要介紹了Springboot中使用Redis實現(xiàn)分布式鎖的示例代碼,具有一定的參考價值,感興趣的可以了解一下2024-05-05