SQL美化器优化

文章目录

1.目录

2.代码

java 复制代码
package com.sunxiansheng.mybatis.plus.inteceptor;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.vertical_blank.sqlformatter.SqlFormatter;
import com.github.vertical_blank.sqlformatter.languages.Dialect;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.sql.Statement;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;

/**
 * SQL美化拦截器:显示完整的SQL,并输出执行结果
 */
@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 SqlBeautyInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(SqlBeautyInterceptor.class);

    // ANSI 颜色代码
    private static final String ANSI_RESET = "\u001B[0m";
    private static final String ANSI_BRIGHT_GREEN = "\u001B[92m";   // 亮绿色(结果)
    private static final String ANSI_BRIGHT_BLUE = "\u001B[94m";    // 亮蓝色(项目名称)

    // Gson 实例,用于格式化结果
    private static final Gson GSON = new GsonBuilder()
            .setPrettyPrinting()
            .disableHtmlEscaping()
            .create();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

        // 分离代理对象链
        while (metaObject.hasGetter("h")) {
            Object object = metaObject.getValue("h");
            metaObject = SystemMetaObject.forObject(object);
        }
        // 分离最后一个代理对象的目标类
        while (metaObject.hasGetter("target")) {
            Object object = metaObject.getValue("target");
            metaObject = SystemMetaObject.forObject(object);
        }

        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        Configuration configuration = mappedStatement.getConfiguration();

        long startTime = System.currentTimeMillis();
        Object result = null;
        try {
            // 执行 SQL,获取结果
            result = invocation.proceed();
            return result;
        } finally {
            long endTime = System.currentTimeMillis();
            long sqlCost = endTime - startTime;
            SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();

            // 使用修改后的 formatSql 方法
            String formattedSql = formatSql(configuration, boundSql);

            // 格式化执行结果
            String formattedResult = formatResult(result, sqlCommandType);

            // 日志输出
            logger.info("\n========================\n{}SQL 类型:{} {}{}{}\n{}SQL:{}\n{}{}{}\n{}执行耗时:{} {}{}ms{} \n{}结果:{}\n{}{}\n{}========================{}",
                    ANSI_BRIGHT_BLUE, ANSI_RESET,
                    ANSI_BRIGHT_GREEN, sqlCommandType, ANSI_RESET,
                    ANSI_BRIGHT_BLUE, ANSI_RESET,
                    ANSI_BRIGHT_GREEN, formattedSql, ANSI_RESET,
                    ANSI_BRIGHT_BLUE, ANSI_RESET,
                    ANSI_BRIGHT_GREEN, sqlCost, ANSI_RESET,
                    ANSI_BRIGHT_BLUE, ANSI_RESET,
                    ANSI_BRIGHT_GREEN, formattedResult, ANSI_RESET,
                    ANSI_BRIGHT_BLUE, ANSI_RESET);
        }
    }

    @Override
    public Object plugin(Object target) {
        // 使用 Plugin.wrap 生成代理对象
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以通过配置文件设置属性
    }

    /**
     * 格式化 SQL,将参数替换到 SQL 中,并使用 SQLFormatter 进行格式化
     *
     * @param configuration MyBatis 配置对象
     * @param boundSql      绑定的 SQL
     * @return 格式化后的 SQL
     */
    private String formatSql(Configuration configuration, BoundSql boundSql) {
        try {
            // 获取 SQL 语句
            String sql = boundSql.getSql();
            // 获取参数对象
            Object parameterObject = boundSql.getParameterObject();
            // 获取参数映射
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

            if (parameterMappings != null && !parameterMappings.isEmpty() && parameterObject != null) {
                TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
                MetaObject metaObject = configuration.newMetaObject(parameterObject);

                // 替换 SQL 中的参数占位符
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    Object value;

                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (metaObject.hasGetter(propertyName)) {
                        value = metaObject.getValue(propertyName);
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        value = null;
                    }

                    sql = replaceFirstQuestionMark(sql, getParameterValue(value));
                }
            }

            // 使用 SQLFormatter 格式化 SQL
            return SqlFormatter.of(Dialect.MySql).format(sql);
        } catch (Exception e) {
            logger.error("Error formatting SQL: ", e);
            return boundSql.getSql();
        }
    }

    private String replaceFirstQuestionMark(String sql, String value) {
        return sql.replaceFirst("\\?", Matcher.quoteReplacement(value));
    }

    private String getParameterValue(Object obj) {
        if (obj == null) {
            return "null";
        }
        if (obj instanceof String) {
            return "'" + obj + "'";
        }
        if (obj instanceof Number) {
            return obj.toString();
        }
        if (obj instanceof java.util.Date || obj instanceof java.sql.Date || obj instanceof java.sql.Timestamp) {
            return "'" + obj.toString() + "'";
        }
        return "'" + obj.toString() + "'";
    }

    /**
     * 格式化 SQL 执行结果
     *
     * @param result         执行结果
     * @param sqlCommandType SQL 类型
     * @return 格式化后的结果字符串
     */
    private String formatResult(Object result, SqlCommandType sqlCommandType) {
        if (result == null) {
            return "null";
        }
        try {
            String formattedResult;
            if (sqlCommandType == SqlCommandType.SELECT) {
                // 查询语句,结果通常是 List
                formattedResult = GSON.toJson(result);
            } else if (sqlCommandType == SqlCommandType.UPDATE || sqlCommandType == SqlCommandType.DELETE || sqlCommandType == SqlCommandType.INSERT) {
                // 更新语句,结果是受影响的行数
                formattedResult = result.toString() + " 行受影响";
            } else {
                // 其他类型
                formattedResult = result.toString();
            }

            // 可根据需要限制结果输出长度,避免日志过大
            int maxLength = 2000;
            if (formattedResult.length() > maxLength) {
                formattedResult = formattedResult.substring(0, maxLength) + "... [结果过长,已截断]";
            }
            return formattedResult;
        } catch (Exception e) {
            logger.error("Error formatting result: ", e);
            return result.toString();
        }
    }
}
相关推荐
要开心吖ZSH25 分钟前
一次线上锁风暴复盘:号源表大范围 DELETE 的隐患与分批删除改造
数据库·mysql·行锁·1024程序员节·间隙锁·lockwaittimeout
莫叫石榴姐1 小时前
半导体业务数仓建模: Lot、Wafer、Die、Recipe、OEE、YMS 建模要点
数据仓库·sql·面试·职场和发展·矩阵·1024程序员节
l1t2 小时前
在windows中测试musql数据库
数据库·sqlite·go·1024程序员节
鬼手点金2 小时前
从 API 消息到模型 Token:Chat Template
java·前端·javascript·数据库·人工智能·深度学习·1024程序员节
CRMEB系统商城2 小时前
Redis + 消息队列 + 连接池:CRMEB PRO 性能优化的三驾马车
大数据·数据库·人工智能·小程序·php·1024程序员节
鸽芷咕2 小时前
KingbaseES SQL 调优六步法:定位、诊断、下药、验证,一步都别省
数据库
黄俊懿3 小时前
【架构师从入门到进阶】第七章:缓存和存储的优化思路——第一节:缓存概述
数据结构·数据库·分布式·缓存·架构·系统架构·1024程序员节
John_Hui3 小时前
MySQL 进阶知识
数据库·sql·mysql·1024程序员节
java_nnnn3 小时前
Redis 核心特性与应用场景:从底层原理到分布式系统应用
数据库·redis·1024程序员节
Random Glow3 小时前
一条 SQL 从输入到返回,中间经历了什么
数据库·后端·mysql·性能优化·innodb·1024程序员节