自己实现一个mybaits日志打印

我们在使用springboot + mybatis的时候,会发现官方提供的日志打印是debug级别的,而且不一定符合自己的日志习惯.因此基于mybatis的 Interceptor,可以自己实现一个完整sql打印 + sql执行耗时的小功能.不多说直接上代码

package com.ke.jiaoyi.ordercore.common.interceptor;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.db.sql.SqlUtil;
import com.google.common.collect.Lists;
import com.ke.jiaoyi.ordercore.common.util.Services;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
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.ResultHandler;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Value;

import java.sql.Statement;
import java.util.Date;
import java.util.List;
import java.util.Properties;

/**
 * Sql执行时间记录拦截器
 */

@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
	@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
	@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})})
public class SqlCostInterceptor implements Interceptor {

	@Value("${sql.format:false}")
	private boolean sqlFormat;

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		Object target = invocation.getTarget();
		long startTime = System.currentTimeMillis();
		StatementHandler statementHandler = (StatementHandler) target;
		try {
			return invocation.proceed();
		} finally {
			printSqlLog(startTime, statementHandler);
		}
	}

	/**
	 * 打印sql 日志
	 */
	public void printSqlLog(long startTime, StatementHandler statementHandler) {
		long endTime = System.currentTimeMillis();
		long sqlCost = endTime - startTime;

		BoundSql boundSql = statementHandler.getBoundSql();
		Object parameterObject = boundSql.getParameterObject();
		String sql = boundSql.getSql();

		// 格式化Sql语句,去除换行符,替换参数
		String formattedSql = null;
		try {
			formattedSql = getFllSql(boundSql, parameterObject);
		} catch (Exception e) {
			LOGGER.warn("Failed to format SQL: {}", e.getMessage());
		}

		if (formattedSql == null) {
			formattedSql = sql;
		}

		LOGGER.info("SQL: [{}]  Execution time: [{}ms]", formattedSql, sqlCost);
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {

	}


	/**
	 * 获取完整的sql(推荐方案)
	 */
	public String getFllSql(BoundSql boundSql, Object parameterObject) {
		// 从
		SqlSessionFactory sqlSessionFactory = Services.of(SqlSessionFactory.class);
		TypeHandlerRegistry typeHandlerRegistry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry();
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		if (CollectionUtil.isEmpty(parameterMappings)) {
			return null;
		}
		StringBuilder sqlBuilder = new StringBuilder(boundSql.getSql());
		List<Object> paramValues = Lists.newArrayListWithExpectedSize(parameterMappings.size());

		for (ParameterMapping parameterMapping : parameterMappings) {
			if (parameterMapping.getMode() == ParameterMode.OUT) {
				continue;
			}
			String propertyName = parameterMapping.getProperty();
			Object value;
			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 = sqlSessionFactory.getConfiguration().newMetaObject(parameterObject);
				value = metaObject.getValue(propertyName);
			}
			paramValues.add(value);
		}
		for (Object paramValue : paramValues) {
			sqlBuilder.replace(sqlBuilder.indexOf("?"), sqlBuilder.indexOf("?") + 1, formatParamValue(paramValue));
		}
		String sql = sqlBuilder.toString();
		if (sqlFormat) {
			return SqlUtil.formatSql(sql);
		} else {
			return sql;
		}
	}

	public String formatParamValue(Object paramValue) {
		if (paramValue == null) {
			return "null";
		} else if (paramValue instanceof String || paramValue instanceof Date) {
			return "'" + paramValue.toString() + "'";
		} else {
			return paramValue.toString();
		}
	}
}

代码说明,基于mybatis的拦截器,在sql执行完后,打印完整sql语句和耗时.其中Services类是一个Spring applicationContext 的工具类,就不提供了.

SqlUtil是hutool工具包里面的,自行依赖hutool即可.

下面是sql执行效果

2023-03-30 15:21:16.110  INFO 14064 --- [nio-8080-exec-3] c.k.j.o.c.i.SqlCostInterceptor           : SQL: [SELECT id,content,created_at,updated_at  FROM t_document  WHERE  id = 404]  Execution time: [14ms]

### 回答: **是的,MyBatis Log Plugin(如:`MyBatis Log Plugin` 或 `Free MyBatis Tool` 中的日志功能)必须依赖 MyBatis 正确输出 SQL 日志,才能提取并格式化显示可执行的 SQL 语句。** 换句话说:**你必须开启 MyBatis日志打印功能,插件才能“看到”原始 SQL、参数和执行时间等信息**,否则插件无法工作。 --- ### ✅ 原理解释 MyBatis Log Plugin 并不会直接拦截或运行你的数据库操作,它的原理是: > **监听控制台(Console)中由 MyBatis 框架输出的调试日志(DEBUG 级别),然后通过正则解析出 SQL 模板和参数值,最终拼接成一条可以直接在数据库客户端执行的完整 SQL 语句。** 所以如果: - MyBatis 没有输出 SQL 日志; - 或日志级别不是 `DEBUG`; - 或使用的日志实现不被插件识别(比如某些自定义包装); 👉 那么插件就“看不到”任何内容,自然无法生成可执行 SQL。 --- ### ✅ 如何正确配置 MyBatis 日志以支持插件使用? 你需要确保以下几点配置都正确: #### 1. 开启 MyBatis 日志输出(推荐使用 StdOutImpl) 在 `application.yml` 或 `application.properties` 中配置: ```yaml mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl ``` 或者在 XML 配置中: ```xml <setting name="logImpl" value="STDOUT_LOGGING"/> ``` ✅ 这会让 MyBatis 将 SQL 打印到控制台,格式如下: ``` ==> Preparing: SELECT * FROM user WHERE id = ? ==> Parameters: 123(Long) <== Total: 1 ``` 📌 插件正是靠这些日志来提取 SQL 和参数。 --- #### 2. 确保日志级别为 DEBUG Spring Boot 默认可能只显示 INFO 及以上级别日志,你需要显式设置 MyBatis 相关包的日志级别为 `DEBUG`。 在 `application.yml` 中添加: ```yaml logging: level: com.yourcompany.mapper: debug # 替换为你的 Mapper 接口所在包 org.mybatis: debug ``` 或者更细粒度: ```yaml logging: level: your.package.name.UserMapper: debug ``` 这样 MyBatis 才会真正输出 SQL 到控制台。 --- #### 3. 使用支持的日志实现 插件能识别的日志格式有限,常见支持的包括: - `StdOutImpl`(推荐) - Log4j2 - SLF4J(需正确配置输出格式) ⚠️ 如果你用了 AOP、自定义日志拦截器或其他方式屏蔽了 SQL 输出,插件也无法获取。 --- ### ✅ 插件工作流程示意 ``` [Java代码] → [MyBatis执行SQL] → [StdOutImpl打印SQL到控制台] → [IDEA控制台显示日志] → [MyBatis Log Plugin监听控制台文本] → [解析SQL + 参数] → [拼接成完整SQL展示给用户] ``` 因此:**没有日志输出 = 插件无数据源 = 功能失效** --- ### ✅ 常见问题排查 | 问题现象 | 可能原因 | 解决方案 | |--------|---------|----------| | 插件没反应,不显示 SQL | MyBatis 未输出日志 | 检查 `log-impl` 和日志级别 | | 显示“Not found SQL” | 控制台日志被过滤 | 确保 Mapper 包设为 `debug` | | 参数未替换(仍是 ?) | 插件无法解析日志格式 | 改用 `StdOutImpl`,避免美化插件干扰 | | 多数据源时部分不生效 | 日志配置未覆盖所有 SqlSession | 统一配置或使用第三方日志框架 | --- ### ✅ 总结 > **MyBatis Log Plugin 必须依赖 MyBatis 开启日志打印功能才能正常工作。** > > 它本身只是一个日志解析器”,不能脱离 MyBatis 的 DEBUG 日志独立运行。 > > ✅ 正确做法: > 同时开启: > 1. `mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl` > 2. 对应 Mapper 包的 `logging.level.xxx=debug` 这样才能让插件成功捕获并格式化 SQL。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值