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就 ...
随机推荐
- Redux Toolkit 的使用方法
Redux Toolkit 是什么? Redux Toolkit 是 Redux 官方强烈推荐,开箱即用的一个高效的 Redux 开发工具集.它旨在成为标准的 Redux 逻辑开发模式,我们强烈建议你 ...
- ASP.NET Core - 依赖注入(二)
.NET Core 依赖注入的基本用法 话接上篇,这一章介绍 .NET Core 框架自带的轻量级 Ioc 容器下服务使用的一些知识点,大家可以先看看上一篇文章 [ASP.NET Core - 依赖注 ...
- 有理数四则运算 PTA1034
题目:https://pintia.cn/problem-sets/994805260223102976/problems/994805287624491008 本题要求编写程序,计算 2 个有理数的 ...
- dom添加样式可以这样写
1.原生 js添加样式很多时可以合并在一起写: var oPopwin = document.getElementById('vpage'); oPopwin.style.margin = 'init ...
- 手机在线编程软件Anycodes
下载地址:http://ys-f.ys168.com/608949803/V6gUhjk3V382275HM63/%E6%89%8B%E6%9C%BA%E7%BC%96%E7%A8%8B%E8%BD% ...
- 通过命令上传到GitHub
从零开始命令行上传代码到GitHub 前情概要: 要提交代码到GitHub上,本来想要通过idea上传代码,然后去网上搜索了相关的文章,按照步骤一步一步的操作,结果还是没有弄好,也会出现各种各样的 ...
- DRF排序
排序 对于列表数据,REST framework提供了OrderingFilter过滤器来帮助我们快速指明数据按照指定字段进行排序. 使用方法: 在类视图中设置filter_backends, 使用 ...
- 前端 ArrayBuffer 与 Blob 互转
我们在使用ajax向后端发送请求时,responseType可以设置返回数据的格式,它支持的格式有"text"."arraybuffer"."blob ...
- c++获取类型信息
获取类型信息 typeid typeid运算符用来获取一个表达式的类型信息. 对于基本类型数据, 类型信息比较简单, 主要指数据的类型; 对于对象(类类型的数据), 类型信息指: 对象所属的类, 所包 ...
- 升级grafana
We recommend everyone to upgrade Grafana often to stay up to date with the latest fixes and enhancem ...