使用MyBatisMyBatis Plus实现SQL日志打印与执行监控

news/2025/2/13 9:56:37/

使用MyBatis/MyBatis Plus实现SQL日志打印与执行监控

一、背景与价值

在开发过程中,SQL日志的完整输出对于调试和性能优化至关重要。MyBatis默认的日志输出仅显示带占位符的SQL语句,无法直接看到实际参数值,且缺乏执行时间统计。本文将介绍两种实现方案:

  1. 原生配置方案:通过日志框架直接输出基础SQL日志
  2. 增强方案:使用MyBatis拦截器实现完整SQL打印和执行监控

二、原生配置方案(快速上手)

1. 日志框架配置(以Logback为例)

<!-- logback-spring.xml -->
<configuration><logger name="com.zaxxer.hikari" level="INFO"/><logger name="java.sql.Connection" level="INFO"/><logger name="java.sql.Statement" level="DEBUG"/><logger name="java.sql.PreparedStatement" level="DEBUG"/>
</configuration>

2. 输出示例

java">DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE name = ?
DEBUG [main] - ==> Parameters: John(String)

3. 局限性

  • 参数值单独显示,无法直接拼接完整SQL
  • 缺乏执行耗时统计
  • 动态SQL处理不够直观

三、增强方案:自定义拦截器实现

1. SQL美化与参数替换

java">public class MybatisPlusAllSqlLog implements InnerInterceptor {public static final Logger log = LoggerFactory.getLogger("sys-sql");@Overridepublic void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {logInfo(boundSql, ms, parameter);}@Overridepublic void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameter);logInfo(boundSql, ms, parameter);}private static void logInfo(BoundSql boundSql, MappedStatement ms, Object parameter) {try {log.info("parameter = " + parameter);// 获取到节点的id,即sql语句的idString sqlId = ms.getId();log.info("sqlId = " + sqlId);// 获取节点的配置Configuration configuration = ms.getConfiguration();// 获取到最终的sql语句String sql = getSql(configuration, boundSql, sqlId);log.info("完整的sql:{}", sql);} catch (Exception e) {log.error("异常:{}", e.getLocalizedMessage(), e);}}// 封装了一下sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId) {return sqlId + ":" + showSql(configuration, boundSql);}// 进行?的替换public static String showSql(Configuration configuration, BoundSql boundSql) {// 获取参数Object parameterObject = boundSql.getParameterObject();List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();// sql语句中多个空格都用一个空格代替String sql = boundSql.getSql().replaceAll("[\\s]+", " ");if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {// 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();// 如果根据parameterObject.getClass()可以找到对应的类型,则替换if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {sql = sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(parameterObject)));} else {// MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作MetaObject metaObject = configuration.newMetaObject(parameterObject);for (ParameterMapping parameterMapping : parameterMappings) {String propertyName = parameterMapping.getProperty();if (metaObject.hasGetter(propertyName)) {Object obj = metaObject.getValue(propertyName);sql = sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(obj)));} else if (boundSql.hasAdditionalParameter(propertyName)) {// 该分支是动态sqlObject obj = boundSql.getAdditionalParameter(propertyName);sql = sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(obj)));} else {// 打印出缺失,提醒该参数缺失并防止错位sql = sql.replaceFirst("\\?", "缺失");}}}}return sql;}// 如果参数是String,则添加单引号, 如果是日期,则转换为时间格式器并加单引号; 对参数是null和不是null的情况作了处理private static String getParameterValue(Object obj) {String value;if (obj instanceof String) {value = "'" + obj.toString() + "'";} else if (obj instanceof Date) {DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT,DateFormat.DEFAULT, Locale.CHINA);value = "'" + formatter.format(new Date()) + "'";} else {if (obj != null) {value = obj.toString();} else {value = "";}}return value;}}

2. 执行耗时监控

java">@Intercepts({@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, CacheKey.class, BoundSql.class})})
public class SqlStatementInterceptor implements Interceptor {public static final Logger log = LoggerFactory.getLogger("sys-sql");@Overridepublic Object intercept(Invocation invocation) throws Throwable {long startTime = System.currentTimeMillis();try {return invocation.proceed();} finally {long timeConsuming = System.currentTimeMillis() - startTime;log.info("执行SQL:{}ms", timeConsuming);if (timeConsuming > 999 && timeConsuming < 5000) {log.info("执行SQL大于1s:{}ms", timeConsuming);} else if (timeConsuming >= 5000 && timeConsuming < 10000) {log.info("执行SQL大于5s:{}ms", timeConsuming);} else if (timeConsuming >= 10000) {log.info("执行SQL大于10s:{}ms", timeConsuming);}}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}

自定义拦截器之后,请注意配置该拦截器

3.输出示例

java">INFO  [http-nio-8080-exec-1] - SQLID: com.example.mapper.UserMapper.selectById
INFO  [http-nio-8080-exec-1] - 完整SQL: SELECT id,name,age FROM user WHERE id=1
INFO  [http-nio-8080-exec-1] - 执行耗时: 48ms
WARN  [http-nio-8080-exec-1] -SQL警告: 执行耗时1204ms

四.总结

通过合理配置SQL日志输出,开发者可以:

  • 快速定位SQL执行问题
  • 直观分析实际执行的SQL语句
  • 有效识别性能瓶颈
  • 提升动态SQL调试效率

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

相关文章

SpringAI-开启 Java AI 新纪元

作为Java开发者&#xff0c;从SpringAI开始&#xff0c;从0开始认识AI&#xff1a; 模型&#xff08;Model&#xff09;提示&#xff08;Prompt&#xff09;提示词模板&#xff08;Prompt Template&#xff09;嵌入&#xff08;Embedding&#xff09;Token结构化输出&#xff…

Net跨平台硬件信息查询库 Hardware.Info:全面获取系统硬件详情

Hardware.Info 是一个基于 .NET Standard 2.0 的跨平台库&#xff0c;提供了硬件信息查询的功能&#xff0c;支持 Windows、Linux 和 macOS 操作系统。该库通过不同平台下的系统接口获取硬件信息&#xff0c;包括电池、电池管理、BIOS、CPU、存储驱动、键盘、内存、显示器、主板…

Oracle常用导元数据方法

1 说明 前两天领导发邮件要求导出O库一批表和索引的ddl语句做国产化测试&#xff0c;涉及6个系统&#xff0c;6千多张表&#xff0c;还好涉及的用户并不多&#xff0c;要不然很麻烦。 如此大费周折原因&#xff0c;是某国产库无法做元数据迁移。。。额&#xff0c;只能我手动导…

ChatGPT怎么回事?

纯属发现&#xff0c;调侃一下~ 这段时间deepseek不是特别火吗&#xff0c;尤其是它的推理功能&#xff0c;突发奇想&#xff0c;想用deepseek回答一些问题&#xff0c;回答一个问题之后就回复服务器繁忙&#xff08;估计还在被攻击吧~_~&#xff09; 然后就转向了GPT&#xf…

MySQL InnoDB引擎 MVCC

MVCC&#xff08;Multi-Version Concurrency Control&#xff09;即多版本并发控制&#xff0c;是 MySQL 的 InnoDB 存储引擎实现并发控制的一种重要技术。它在很多情况下避免了加锁操作&#xff0c;从而提高了数据库的并发性能。 一、原理 MVCC 的核心思想是通过保存数据在某…

使用DeepSeek+本地知识库,尝试从0到1搭建高度定制化工作流(需求分析篇)

1.需求分析 数据爬取 -> 数据清洗 -> 数据存储为文本文件文本切片 -> 文本嵌入模型 -> 向量数据库用户提问 -> 数据召回 -> 注入Prompt -> 生成回答生成文案 -> 生成配图 -> 发布到平台 2.RAG流程 3.目录结构(预设) . ├── xiaohongshu_drafts…

Netty的线程模型详解

引言 Netty 是一个高性能、异步事件驱动的网络应用框架&#xff0c;广泛应用于各种网络服务器和客户端的开发。它基于Java NIO&#xff08;Non-blocking I/O&#xff09;技术&#xff0c;能够高效处理大量并发连接和高吞吐量的网络通信。Netty 的核心之一就是它的线程模型&…

又要pde。。

五分文件 文件 1&#xff1a;The energy technique for the six-step BDF method.pdf 这篇论文研究了六阶 BDF 方法的稳定性分析&#xff0c;并将其应用于抛物线方程的数值解。主要内容包括&#xff1a; 引言&#xff1a;介绍了 BDF 方法的基本原理和六阶 BDF 方法的特性&…