Spring Boot + MyBatis 实现 SQL 美化插件(含参数替换 + 执行耗时 + 慢 SQL 告警)

共计 3053 个字符,预计需要花费 8 分钟才能阅读完成。

一、为什么需要美化 SQL 插件?

select id,name from user where name = ? and status = ?

这类日志存在以下痛点:

  • 无法看懂绑定了哪些参数
  • 一行 SQL 没有格式,难以排查问题
  • 看不到 SQL 执行耗时,难以做性能分析

为此,我们实现一个 MyBatis 插件,具备如下特性:

二、实现思路

我们将基于 MyBatis 的拦截器机制 Interceptor 实现,对 StatementHandler#prepare 方法进行拦截,在 SQL 执行前后记录:

  • 原始 SQL
  • 替换参数后的 SQL
  • 执行耗时
  • 判断是否慢 SQL

三、核心代码实现

1. 自定义拦截器 SqlBeautifyInterceptor

@Slf4j
@Intercepts({
    @Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class}
    )
})
@Component
public class SqlBeautifyInterceptor implements Interceptor {

    // 慢 SQL 阈值(毫秒),建议配置化
    private static final long SLOW_SQL_THRESHOLD = 500;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();

        // 获取原始 StatementHandler
        StatementHandler statementHandler = (StatementHandler) getRealTarget(invocation.getTarget());
        BoundSql boundSql = statementHandler.getBoundSql();

        String rawSql = boundSql.getSql(); // 带 ? 的 SQL
        String formattedSql = SqlFormatter.format(rawSql); // 美化 SQL
        String finalSql = replaceParams(formattedSql, boundSql); // 替换参数

        Object result = invocation.proceed(); // 执行 SQL

        long timeCost = System.currentTimeMillis() - start;

        // 输出日志(包含慢 SQL 检测)
        if (timeCost > SLOW_SQL_THRESHOLD) {
            log.warn("\n[SLOW SQL - {} ms]\n{}", timeCost, finalSql);
        } else {
            log.info("\n[SQL - {} ms]\n{}", timeCost, finalSql);
        }

        return result;
    }

    // 参数替换:将 ? 替换为实际参数
    private String replaceParams(String sql, BoundSql boundSql) {
        List<ParameterMapping> mappings = boundSql.getParameterMappings();
        Object paramObject = boundSql.getParameterObject();
        if (mappings == null || mappings.isEmpty() || paramObject == null) {
            return sql;
        }

        MetaObject metaObject = SystemMetaObject.forObject(paramObject);
        for (ParameterMapping mapping : mappings) {
            String property = mapping.getProperty();
            Object value;

            if (boundSql.hasAdditionalParameter(property)) {
                value = boundSql.getAdditionalParameter(property);
            } else if (metaObject.hasGetter(property)) {
                value = metaObject.getValue(property);
            } else {
                value = null;
            }

            sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(formatValue(value)));
        }
        return sql;
    }

    // 将参数格式化为字符串
    private String formatValue(Object value) {
        if (value == null) return "NULL";
        if (value instanceof String || value instanceof Date) {
            return "'" + value.toString().replace("'", "''") + "'";
        }
        return value.toString();
    }

    // 解除代理包装
    private Object getRealTarget(Object target) {
        MetaObject metaObject = SystemMetaObject.forObject(target);
        while (metaObject.hasGetter("h")) {
            Object object = metaObject.getValue("h");
            metaObject = SystemMetaObject.forObject(object);
        }
        return metaObject.getOriginalObject();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {}
}

2. SQL 美化工具类 SqlFormatter

public class SqlFormatter {
    public static String format(String sql) {
        try {
            return new BasicFormatterImpl().format(sql);
        } catch (Exception e) {
            return sql; // 出错则返回原 SQL
        }
    }
}

依赖 Hibernate 的格式化器(当然,这里的sql格式化也可以根据自己的要求增强)

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.4.10.Final</version>
</dependency>

四、输出效果演示

正常 SQL 输出:

SELECT * FROM user WHERE name = ? AND age > ?

日志输出:

[SQL - 23 ms]
select
    *
from
    user
where
    name = '张三'
    and age > 18

五、总结

通过自定义 MyBatis 插件,我们实现了:

✅ SQL 可读性提升(美化输出)
✅ 参数调试更方便(真实值替换)
✅ 性能问题早发现(耗时 & 慢 SQL 告警)
✅ 插件可拓展性强,适合企业统一规范

如需完整 demo 工程欢迎评论区留言「SQL插件」,我可以打包发你 ❤️


我是 李卷卷,专注Java相关知识输出。感谢阅读!

正文完
 0
李卷卷
版权声明:本站原创文章,由 李卷卷 于2023-11-20发表,共计3053字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)