用mybatis插件抓取最终sql

痛点概述

当我们在排查bug ,需要看执行的完整sql 时,在 console可以拿到如图的sql img

然后手工一个一个的替换问号占位符后,去MySQL 执行,看sql有木有什么问题。如果sql简单,那比较好说, 如果是个复杂sql,手动替换N个问号占位符,这种痛相信大家都经历过。 今天介绍的 MybatisFinalSqlPlugin 插件 正是解决了这样的痛点,可以直接抓到最终sql。

MybatisFinalSqlPlugin插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

package com.anuo.app.common.datalayer.mybatisplugin;

import com.google.common.base.Stopwatch;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Properties;

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


@Override
public Object intercept(Invocation invocation) throws Throwable {
Stopwatch stopwatch = Stopwatch.createStarted();

if (invocation.getTarget() instanceof StatementHandler) {

//获取Statement 对象
Connection conn = (Connection) invocation.getArgs()[0];
StatementHandler handler = (StatementHandler) invocation.getTarget();
Statement stmt = handler.prepare(conn, 30);
handler.parameterize(stmt);

//获取真实对象
MetaObject metaObject = SystemMetaObject.forObject(stmt);

while (metaObject.hasGetter("h")) {
Object obj = metaObject.getValue("h");
// 将对象包装成MetaObject对象后就成了真实对象,然后就可以通过反射技术可以操作真实对象的所有属性
metaObject = SystemMetaObject.forObject(obj);
}

//通过反射获取 Statement 对象上的FinalSql
//todo:后续用 metaObject.getValue() 重构下面的反射代码,提升性能

Field hField = metaObject.getClass().getDeclaredField("originalObject");
hField.setAccessible(true);
Object hObj = hField.get(metaObject);

Field statementField = hObj.getClass().getDeclaredField("statement");
statementField.setAccessible(true);
Object statementObj = statementField.get(hObj);

Field stmtField = statementObj.getClass().getDeclaredField("stmt");
stmtField.setAccessible(true);
Object stmtObj = stmtField.get(statementObj);

Field statementArrivedField = stmtObj.getClass().getDeclaredField("statement");
statementArrivedField.setAccessible(true);
Object statementArrivedFieldObj = statementArrivedField.get(stmtObj);


String finalSql = statementArrivedFieldObj.toString();

//去掉不要的字符串
finalSql = finalSql.substring(finalSql.lastIndexOf(":") + 1, finalSql.length() - 1);

log.info("最终sql: \n " + finalSql);

}

//做了下性能测试 平均耗时为 1,2毫秒,非常低,不错!
log.debug("抓取最终sql 耗时:" + stopwatch);
return invocation.proceed();
}



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

@Override
public void setProperties(Properties properties) {

}