换种方式,打印 MyBatis 执行 SQL 语句!

作者:小傅哥
博客:https://bugstack.cn
 

沉淀、分享、成长 , 让自己和他人都能有所收获!
一、前言 
片面了!
一月三舟 , 托尔斯泰说:“多么伟大的作家 , 也不过就是在书写自己的片面而已” 。何况是我 , 何况是我们!
虽然我们不书写文章 , 但我们写需求、写代码、写注释 , 当我们遇到了需要被讨论的问题点时 , 往往变成了争论点 。这个好、那个差、你用的都是啥啥啥!
当你把路走窄了 , 你所能接受到的新的思路、新的想法、新的视野 , 以及非常重要的收入 , 也都会随之减少 。只有横向对比、参考借鉴、查漏补缺 , 才能让你的头脑中会有更多的思路 , 无论是在写代码上、还是在理财上、还是在生活上 。
二、需求目的
你是否有在使用 IntelliJ IDEA 做开发的过程 , 需要拿到执行 SQL 语句 , 复制出来做验证的时候 , 总是这样的语句:SELECT * FROM USER WHERE id = ? AND name = ? 又需要自己把 ? 号 替换成入参值呢?
当然这个需求其实并不大 , 甚至你还可以使用其他方式解决 。那么在本章节会给你提供一个新的思路 , 可能你几乎是没过的方式进行处理 。
那么在这个章节的案例中我们用到基于 IDEA Plugin 开发能力 , 把字节码插桩探针 , 基于 JAVAagent 的能力 , 注入到代码中 。再通过增强后的字节码 , 获取到 com.MySQL.jdbc.Preparedstatement -> executeInternal 执行时的对象 , 从而拿到可以直接测试的 SQL 语句 。
三、案例开发 1. 工程结构 guide-idea-plugin-probe ├── .gradle ├── probe-agent │ ├── src │ │ └── main │ │ └── java │ │ └── cn.bugstack.guide.IDEA.plugin │ │ ├── MonitorMethod.java │ │ └── PreAgent.java │ └── build.gradle └── probe-plugin │ └── src │ │ └── main │ │ ├── Java │ │ │ └── cn.bugstack.guide.idea.plugin │ │ │ └── utils │ │ │ │ └── PluginUtil.java │ │ │ └── PerRun.java │ │ └── resources │ │ └── META-INF │ │ └── plugin.xml │ └── build.gradle ├── build.gradle └── gradle.properties
公众号:bugstack虫洞栈 回复:idea 即可下载全部 IDEA 插件开发源码
在此 IDEA 插件工程中 , 工程结构分为2块:
 
  • probe-agent:探针模块 , 用于编译打包提供字节码增强服务 , 给 probe-plugin 模块使用
  • probe-plugin:插件模块 , 通过 java.programPatcher 加载字节码增强包 , 获取并打印执行数据库操作的 SQL 语句 。
2. 字节码增强获取 SQL 
此处的字节码增强方式 , 采用的 Byte-Buddy 字节码框架 , 它的使用方式更加简单 , 在使用的过程中有些像使用 AOP 的拦截方式一样 , 获取到你需要的信息 。
此外在 gradle 打包构建的时候 , 需要添加 shadowJar 模块 , 把 Premain-Class 打包进去 。这部分代码中可以查看
2.1 探针入口
cn.bugstack.guide.idea.plugin.PreAgent
//JVM 首先尝试在代理类上调用以下方法 public static void premain(String agentArgs, Instrumentation inst) { AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> { return builder .method(ElementMatchers.named("executeInternal")) // 拦截任意方法 .intercept(MethodDelegation.to(MonitorMethod.class)); // 委托 }; new AgentBuilder .Default() .type(ElementMatchers.nameStartsWith("com.mysql.jdbc.PreparedStatement")) .transform(transformer) .installOn(inst); }
  • 通过 Byte-buddy 配置 , 拦截匹配的类和方法 , 因为这个类和方法下 , 可以获取到完整的执行 SQL 语句 。
2.2 拦截 SQL 
cn.bugstack.guide.idea.plugin.MonitorMethod
@RuntimeType public static Object intercept(@This Object obj, @Origin Method method, @SuperCall Callable callable, @AllArguments Object... args) throws Exception { try { return callable.call(); } finally { String originalSql = (String) BeanUtil.getFieldValue(obj, "originalSql"); String replaceSql = ReflectUtil.invoke(obj, "asSql"); System.out.println("数据库名称:mysql"); System.out.println("线程ID:" + Thread.currentThread().getId()); System.out.println("时间:" + new Date()); System.out.println("原始SQL:rn" + originalSql); System.out.println("替换SQL:rn" + replaceSql); } }


推荐阅读