news 2026/8/28 20:18:47

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Mybatis控制台打印SQL执行信息(执行方法、执行SQL、执行时间)
文章目录
  • 前言
  • 一、基本功能介绍
    • 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; @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(" ", ""); 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(); } @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("'", "''"); } } }

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/28 5:28:56

C语言第24章 多维数组入门

文章目录 第24章 多维数组入门 本章导读 24.1 二维数组的概念 一、什么是二维数组 二、二维数组的声明 三、理解"数组的数组" 24.2 二维数组的声明与初始化 一、声明方式 二、初始化注意事项 三、代码实践 24.3 二维数组的遍历 一、基本遍历方法 二、行主序 vs 列主序…

作者头像 李华
网站建设 2026/8/25 19:25:34

C语言第25章 字符数组与字符串

文章目录 第25章 字符数组与字符串 本章导读 25.1 字符数组与字符串的关系 一、字符数组 二、字符串 三、关键区别 25.2 字符串的初始化 一、初始化方法 二、初始化注意事项 25.3 字符串的输入输出 一、输出字符串 二、输入字符串 三、常见问题与注意事项 25.4 基本的字符串操作…

作者头像 李华
网站建设 2026/8/19 20:14:37

Asian Beauty Z-Image Turbo 模型解析:从计算机组成原理视角看GPU推理过程

Asian Beauty Z-Image Turbo 模型解析&#xff1a;从计算机组成原理视角看GPU推理过程 最近在部署和测试一些图像生成模型时&#xff0c;我发现很多朋友对“为什么需要这么强的显卡”、“推理时GPU到底在忙什么”这些问题感到困惑。大家可能知道模型参数大、算力要求高&#x…

作者头像 李华
网站建设 2026/8/22 7:12:19

PyTorch 2.5镜像实测:免安装免配置,快速体验GPU训练全流程

PyTorch 2.5镜像实测&#xff1a;免安装免配置&#xff0c;快速体验GPU训练全流程 1. 引言&#xff1a;告别环境配置的噩梦 如果你尝试过在本地电脑上安装PyTorch&#xff0c;大概率经历过这样的痛苦&#xff1a;Python版本不对、CUDA驱动不匹配、各种依赖库冲突&#xff0c;…

作者头像 李华
网站建设 2026/8/19 22:45:17

新手友好,快马AI生成带详解注释的dll修复工具学习项目

对于刚接触编程的朋友来说&#xff0c;听到“DLL修复”可能会觉得既神秘又复杂&#xff0c;感觉要深入到Windows系统的核心才行。确实&#xff0c;手动处理DLL文件涉及到系统API调用、注册表操作、文件权限等一系列难题&#xff0c;对新手很不友好。不过&#xff0c;学习编程的…

作者头像 李华
网站建设 2026/8/24 13:55:42

AI编程:重构的那些事儿

避免 AI 生成混乱的代码 刚开始用 AI 写代码时&#xff0c;一切都很美好——代码清晰简洁&#xff0c;看起来很舒服。但功能越加越多后&#xff0c;代码开始变得混乱。最后连你自己都不敢动这些代码&#xff0c;因为改一个地方&#xff0c;可能会影响到其他地方。 这种情况在…

作者头像 李华