mybatis sql 打印
场景
在开发环境,需要实时查看 mybatis 打印的完整sql ,也可以使用插件手动复制转换,比较麻烦,现在尝试通过 java agent方式进行拦截打印完整 sql , 字节码技术 bytebuddy, 当然也可以使用javaassist
实现
目前支持 mysql 驱动 8.x
引入依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<bytebuddy.version>1.14.18</bytebuddy.version>
<logback.version>1.5.6</logback.version>
</properties>
<dependencies>
<!--sql 格式化-->
<dependency>
<groupId>com.github.vertical-blank</groupId>
<artifactId>sql-formatter</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>${bytebuddy.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>${bytebuddy.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<finalName>mybatis-log</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<!-- 修改为正确的Premain地址 -->
<Premain-Class>org.example.sample.log.SqlLogAgent</Premain-Class>
</manifestEntries>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
SqlLogAgent 定义入口
public class SqlLogAgent {
public static void premain(String agentArgs, Instrumentation inst) {
//目前 mysql 8.x
//拦截 ClientPreparedQuery
new AgentBuilder.Default()
.type(ElementMatchers.named("com.mysql.cj.ClientPreparedQuery"))
.transform((builder, typeDescription, classLoader, module,protectionDomain) ->
builder.method(ElementMatchers.named("fillSendPacket"))
//增加逻辑委托给 MysqlCJLoggerInterceptor
.intercept(MethodDelegation.to(MysqlCJLoggerInterceptor.class)))
.installOn(inst);
}
}
MysqlCJLoggerInterceptor 增强逻辑
@Slf4j
public class MysqlCJLoggerInterceptor {
/*
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onSetParameterEnter(@Advice.Origin("#t") String methodName) throws SQLException {
log.info("agent enter {}", methodName);
System.out.println("agent enter: "+methodName);
}
@Advice.OnMethodExit(suppress = Throwable.class)
public static void onFillSendPacketExit(@Advice.Origin("#t") String methodName,
@Advice.Return Message message) throws SQLException {
try {
log.info("methodName: {}", methodName);
String s = new String(message.getByteBuffer());
log.info("mysql log: {}", s);
} catch (Exception e) {
log.error("onFillSendPacketEnter error:", e);
}
}*/
/* @Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(@Advice.Origin MethodDescription method,
@Advice.This Object instance) {
System.out.println("Method " + method.getName() + " entered in " + instance.getClass().getSimpleName());
}
@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(@Advice.Origin MethodDescription method,
@Advice.Return(readOnly = false) Object returnValue) {
System.out.println("Method " + method.getName() + " exited in " + method.getDeclaringType().asErasure().getInternalName());
}*/
//========================以上都没有效果=======================//
//拦截运行时
@RuntimeType
public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable) throws Exception {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Message call = (Message) callable.call();
stopWatch.stop();
String sql = new String(call.getByteBuffer());
formatSqlLog(sql.trim(),stopWatch);
return call;
}
/**
* 格式化 sql 日志
* @param sql
* @param stopWatch
*/
private static void formatSqlLog(String sql, StopWatch stopWatch) {
String format = "\n--------------------【sql log】---------------------\n\n {} \n\n耗时: {}";
log.info(format,sql, prettyPrint(stopWatch));
}
/**
* 格式化
*
* @param stopWatch
* @return
*/
public static String prettyPrint(StopWatch stopWatch) {
StringBuilder sb = new StringBuilder(stopWatch.getTotalTimeMillis() + "ms");
sb.append(" ");
sb.append(stopWatch.getTotalTimeNanos() + "ns");
sb.append('\n');
if (stopWatch.getTaskInfo() == null) {
sb.append("No task info kept");
} else {
sb.append("---------------------------------------------\n");
sb.append("ns % Task name\n");
sb.append("---------------------------------------------\n");
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMinimumIntegerDigits(9);
nf.setGroupingUsed(false);
NumberFormat pf = NumberFormat.getPercentInstance();
pf.setMinimumIntegerDigits(3);
pf.setGroupingUsed(false);
for (StopWatch.TaskInfo task : stopWatch.getTaskInfo()) {
sb.append(nf.format(task.getTimeNanos())).append(" ");
sb.append(pf.format((double) task.getTimeNanos() / stopWatch.getTotalTimeNanos())).append(" ");
sb.append(task.getTaskName()).append("\n");
}
}
return sb.toString();
}
}
测试
在项目启动时加入 -javaagent:D:\xx\mybatis-log.jar
, 结果:
--------------------【sql log】---------------------
select address from t_address
WHERE address like concat('%','shanghai','%')
and is_delete = 0
耗时: 0ms 393200ns
---------------------------------------------
ns % Task name
---------------------------------------------
000393200 100%
good luck !