共计 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相关知识输出。感谢阅读!
正文完