MyBatis(Plus) 打印SQL, 分析执行时间
MyBatis/MyBatis Plus打印的SQL调试起来比较麻烦
当然IDEA/eclipse都有类似mybatis log plugin这种插件来解析, 但是安装插件有些许弊端, 就写了个工具类处理
如果需要更详细的SQL分析, 建议使用p6spy. 本配置仅仅是为了调试SQL方便
效果展示:

代码:
package kim.nzxy.ly.common.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.ibatis.executor.parameter.ParameterHandler;
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.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import java.sql.Statement;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* mybatis sql 日志拦截器, 用于打印SQL
*
* @author ly-chn
*/
@Slf4j
@Profile("local")
@Component
@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 MybatisLogSqlInterceptor implements Interceptor {
private static final Set<String> NEED_BRACKETS =
Set.of("String", "Date", "Time", "LocalDate", "LocalTime", "LocalDateTime", "BigDecimal", "Timestamp");
private Configuration configuration = null;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
long startTime = System.currentTimeMillis();
try {
// 如需打印结果, 返回值即结果集
return invocation.proceed();
} finally {
long sqlCost = System.currentTimeMillis() - startTime;
String sql = this.getSql(target);
log.info("sql took {}ms: {}", sqlCost, sql);
}
}
/**
* 获取sql
*/
private String getSql(Object target) {
try {
StatementHandler statementHandler = (StatementHandler) target;
BoundSql boundSql = statementHandler.getBoundSql();
if (configuration == null) {
final ParameterHandler parameterHandler = statementHandler.getParameterHandler();
this.configuration = (Configuration) FieldUtils.readField(parameterHandler, "configuration", true);
}
// 替换参数格式化Sql语句,去除换行符
return formatSql(boundSql, configuration);
} catch (Exception e) {
log.warn("get sql error {}", target, e);
return "failed to parse sql";
}
}
/**
* 获取完整的sql实体的信息
*/
private String formatSql(BoundSql boundSql, Configuration configuration) {
String sql = boundSql.getSql();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
Object parameterObject = boundSql.getParameterObject();
// 输入sql字符串空判断
if (StringUtils.isEmpty(sql) || Objects.isNull(configuration)) {
return "";
}
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
// 替换空格容易造成本身存在空格的查询条件被替换
sql = sql.replaceAll("[\n\r ]+", " ");
if (parameterMappings == null) {
return sql;
}
parameterMappings = parameterMappings.stream().filter(it -> it.getMode() != ParameterMode.OUT).collect(Collectors.toList());
final StringBuilder result = new StringBuilder(sql);
// 解析问号并填充
for (int i = result.length(); i > 0; i--) {
if (result.charAt(i - 1) != '?') {
continue;
}
ParameterMapping parameterMapping = parameterMappings.get(parameterMappings.size() - 1);
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);
}
if (value != null) {
String type = value.getClass().getSimpleName();
if (NEED_BRACKETS.contains(type)) {
result.replace(i - 1, i, "'" + value + "'");
} else {
result.replace(i - 1, i, value.toString());
}
} else {
result.replace(i - 1, i, "null");
}
parameterMappings.remove(parameterMappings.size() - 1);
}
return result.toString();
}
}
MyBatis(Plus) 打印SQL, 分析执行时间的更多相关文章
- MyBatis 插件 : 打印 SQL 及其执行时间
Plugins 摘一段来自MyBatis官方文档的文字. MyBatis允许你在某一点拦截已映射语句执行的调用.默认情况下,MyBatis允许使用插件来拦截方法调用: Executor(update. ...
- mybatis日志,打印sql语句,输出sql
mybatis日志,打印sql语句,输出sql<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE ...
- mybatis配置打印sql
mybatis配置打印sql: <settings> <setting name="logImpl" value="STDOUT_LOGGING&quo ...
- Springboot中mybatis控制台打印sql语句
Springboot中mybatis控制台打印sql语句 https://www.jianshu.com/p/3cfe5f6e9174 https://www.jianshu.com/go-wild? ...
- Springboot自定义starter打印sql及其执行时间
前面写到了通过实现mybatis提供的org.apache.ibatis.plugin.Interceptor接口实现了打印SQL执行时间,并格式化SQL及其参数,如果我们使用的是ssm还得再配置文件 ...
- mybatis 控制台打印sql
开发时调试使用 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBe ...
- mybatis 控制台打印sql语句
其实很简单,打印SQL只需要加一个setting就可以了.亲测可用. mybatis-config.xml: <settings> <setting name=&quo ...
- spring-mvc Mybatis插件打印SQL
代码: package com.chainup.exchange.service.adapter; import com.chainup.exchange.service.impl.AccountSe ...
- mybatis logback打印sql
<?xml version="1.0" encoding="UTF-8" ?><configuration> <contextNa ...
- Mybatis控制台打印SQL语句的两种方式
问题描述在使用mybatis进行开发的时候,由于可以动态拼接sql,这样大大方便了我们.但是也有一定的问题,当我们动态sql拼接的块很多的时候,我们要想从*mapper.xml中直接找出完整的sql就 ...
随机推荐
- 大曝光!从RabbitMQ平滑迁移至Kafka架构设计方案!
历史原因,公司存在多个 MQ 同时使用的问题,我们中间件团队在去年下半年开始支持对 Kafka 和 Rabbit 能力的进行封装,初步能够完全支撑业务团队使用. 鉴于在之前已经基本完全实施 Kafka ...
- 【KAWAKO】TVM-使用c++进行推理
目录 前言 修改cpp_deploy.cc文件 修改DeployGraphExecutor()函数 numpy与bin文件的互相转换 numpy转bin bin转numpy 使用CMakeLists. ...
- 最新版 IDEA 2022.3.2 最优开发配置
最新版 IDEA 2022.3.2 最优开发配置 教程最后更新时间:2023.3.1 安装好 IntelliJ IDEA 后,进行如下的初始化操作,工作效率提升10倍. 目录 一.全局配置 如何进入全 ...
- LeetCode-2044 统计按位或能得到最大值子集的数目
来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/count-number-of-maximum-bitwise-or-subsets 题目描述 给 ...
- Python elasticsearch 报错及解决方法
1. ERROR: [1] bootstrap checks failed [1]: the default discovery settings are unsuitable for product ...
- 并发JUC
JUC初级 • 1.什么是 JUC • 2.Lock 接口 • 3.线程间通信 • 4.集合的线程安全 • 5.多线程锁 • 6.Callable 接口 • 7.JUC 三大辅助类: CountDow ...
- 「JOI Open 2022」Giraffes 题解
设我们将要给出的观感好的排列为 \(q\),我们希望求出 \(\sum[p_i=q_i]\) 的最大值(这里指不移动的长颈鹿个数). 结论一:当且仅当左右端点有当前区间最大值或者最小值时条件才能成立. ...
- PO/PI
典型集成场景 PI总体架构 消息映射 General Concepts(通用概念) 映射编辑器 元素 分配源/目标消息类型 有3种消息类型可分配给消息映射 1.使用已经存在于IR中的对象(比如消息类型 ...
- NSIS Inetc插件 扩展使用
Inetc客户端插件,用于文件的上传和下载. 官网文档:https://nsis.sourceforge.io/Inetc_plug-in 以下载net包为例 inetc::get "htt ...
- Appium元素选择 滑动 通知栏
一.根据ID 包名可省略 1.元素的resource id属性 2.唯一标志该元素的值 3.一般最优先根据它来定位 driver.find_element_by_id('io.manong.deve ...