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();
        }
    }
}
相关推荐
moMo1 分钟前
# 向量数据库入门:从"关键词匹配"到"语义理解"
数据库
ACP广源盛139246256739 分钟前
DeepSeek‑V4‑Flash 公测@ACP#昇腾 950 国产算力组合落地,国产 PCIe 交换芯片 IX9104 有哪些硬件机会
大数据·数据库·人工智能·分布式·单片机·嵌入式硬件·microsoft
进击的女IT32 分钟前
通过mysql中的Data目录恢复数据库数据
数据库·mysql
yuezhilangniao38 分钟前
AI工具全家桶:从开发到运维,从数据库到产品经理-含魔塔社区简介
运维·数据库·人工智能
gb42152871 小时前
ChromaDB向量数据库的特点
数据库
IvorySQL1 小时前
PostgreSQL 日报| GiST 索引扫描可见性缺陷(8 月 3 日)
数据库·人工智能·postgresql·开源
xiaoxiangsiyan1 小时前
LNMP + Redis Sentinel 高可用架构部署手册(续)
运维·网络·数据库·redis·缓存·架构·sentinel
刘婉晴1 小时前
【Web漏洞】SQL 注入实战技巧
前端·数据库·sql
CodeHackerBhx2 小时前
Spring Boot 4 防重复提交:从接口幂等到 Redis 分布式锁的完整实践
java·数据库·spring boot·redis·分布式
xlxxy_2 小时前
外部系统调用SAP接口遇到的一些报错
开发语言·数据库·sap·abap