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

news/2025/3/11 2:56:06/
文章目录
  • 前言
  • 一、基本功能介绍
    • 1.1本章功能效果预览图:
  • 二、可执行源码
    • 2.1 yaml基础配置
    • 2.2 MybatisAnalyzeSQLInterceptor实现SQL拦截

前言

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;@Overridepublic 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 mappedStatementMappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];// get BoundSqlBoundSql 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 exceptionif (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("
", "");String printData = "
===============Start Print SQL===============
" +"Mapper Method: [ " + statement + " ]
" +"Execute SQL: " + finalSQL + " 
" +"Spend Time: " + spendTime + " ms 
" +"===============End Print SQL===============
";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();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties0) {}@Setter@Getterpublic 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() {}@Overridepublic 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("'", "''");}}
}


http://www.ppmy.cn/news/1578235.html

相关文章

【新人系列】Golang 入门(三):条件循环

✍ 个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4dd; 专栏地址&#xff1a;https://blog.csdn.net/newin2020/category_12898955.html &#x1f4e3; 专栏定位&#xff1a;为 0 基础刚入门 Golang 的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们…

mysql,docker一键创建以及链接报错:Public Key Retrieval is not allowed

创建&#xff1a; docker run --name mysql-test -e MYSQL_ROOT_PASSWORD123 -d -p 13306:3306 mysql:8.0.39 docker exec -it mysql-test /bin/bash mysql -u root -p 报错&#xff1a;Public Key Retrieval is not allowed

PTA 7-6 列出连通集

题目详情&#xff1a; 给定一个有 n 个顶点和 m 条边的无向图&#xff0c;请用深度优先遍历&#xff08;DFS&#xff09;和广度优先遍历&#xff08;BFS&#xff09;分别列出其所有的连通集。假设顶点从 0 到 n−1 编号。进行搜索时&#xff0c;假设我们总是从编号最小的顶点出…

分布式ID生成方案:数据库号段、Redis与第三方开源实现

分布式ID生成方案&#xff1a;数据库号段、Redis与第三方开源实现 引言 在分布式系统中&#xff0c;全局唯一ID生成是核心基础能力之一。本文针对三种主流分布式ID生成方案&#xff08;数据库号段模式、Redis方案、第三方开源框架&#xff09;进行解析&#xff0c;从实现原理…

23种设计模式简介

一、创建型&#xff08;5种&#xff09; 1.工厂方法 总店定义制作流程&#xff0c;分店各自实现特色披萨&#xff08;北京店-烤鸭披萨&#xff0c;上海店-蟹粉披萨&#xff09; 2.抽象工厂 套餐工厂&#xff08;家庭装含大披萨薯条&#xff0c;情侣装含双拼披萨红酒&#…

机器学习数学基础:42.AMOS 结构方程模型(SEM)分析的系统流程

该流程图完整呈现了 AMOS 结构方程模型&#xff08;SEM&#xff09;分析的系统流程&#xff0c;具体步骤及内涵如下&#xff1a; 1. 模型设定 基于理论基础或研究假设&#xff0c;构建结构方程模型的初始框架&#xff0c;明确潜变量与显变量的关系、测量模型&#xff08;因子…

【GPT入门】第8课 大语言模型的自洽性

【GPT入门】第8课 大语言模型的自洽性 1.自洽性概念2.代码&#xff08;观察执行结果&#xff09;3.自洽性核心思想 1.自洽性概念 大模型的自洽性&#xff08;self - consistency&#xff09;是指在推理阶段&#xff0c;大模型通过生成多个答案并选择出现频率最高的那个&#x…

openwrt路由系统------lua、uci的关系

1. Luci 的核心组成 (1) Lua 简介:Luci 的界面和逻辑几乎完全使用 Lua 脚本语言编写。Lua 是一种轻量级、高效的嵌入式脚本语言,适合在资源受限的路由器环境中运行。作用: 生成动态 Web 页面(与后端交互渲染 HTML)。处理用户提交的表单数据(如修改 Wi-Fi 密码)。调用系…