mybatis sql 打印

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 !

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值