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 ... 
- 三星为其基于 RISC-V的 Tizen平台移植.NET
			最近.NET团队在这篇文章中介绍了对.NET移植的一般政策:https://devblogs.microsoft.com/dotnet/why-dotnet/#binary-distributions ... 
- Bouncy Castle密码算法库
			Bouncy Castle密码算法库 一.开发背景 Bouncy Castle 是一种用于 Java 平台的开放源码的轻量级密码术包.它支持大量的密码术算法,并提供 JCE 1.2.1 的实现.因为 ... 
- python cls方法_关于类方法中的cls
			title: python cls方法_关于类方法中的cls author: 杨晓东 permalink: python cls方法_关于类方法中的cls date: 2021-10-02 11:27 ... 
- CF623A Graph and String
			个人思路: 显然,和其他所有点连边的点都是 b.我们接下来不考虑这些点. 剩余 a 和 c 必然自己形成一个连通块,每个点与块内其他所有点连边. 超过 \(2\) 个连通块,或存在点没有与块内其他所有 ... 
- frp使用教程
			内网穿透工具---frp使用教程 https://blog.csdn.net/u011215939/article/details/103383373 
- golang 切片(slice)
			1.切片的定义 切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型. 切片的使用与数组类似,遍历,访问切片元素等都一样.切片是长度是可以变化的,因此切片可以看做是一个动态数组. 一个 ... 
- 基于HttpWebRequest,HttpWebResponse发起请求
			/// <summary> /// 获取版本更新信息 GET /// </summary> /// <param name="softwareKey" ... 
- TensorFlow学习报告
			TensorFlow简介 是一个基于计算图的深度学习库,具有更广泛的应用领域.良好的多语言支持.部署性能等优点,时现今最广泛使用的深度学习框架. 计算图Session Tensor 1 import ... 
- vscode 报错command line option ‘-std=c11‘ is valid for C/ObjC but not for C++
			cc1plus.exe: warning: command-line option '-std=c17' is valid for C/ObjC but not for C++ 编译运行出现警告,原因 ... 
