实现一个mybatis插件,方便在开发中清楚的看出sql的执行及执行耗时


主要实现:

  • 拦截sql执行
  • 记录执行时间
  • 获取sql信息
  • 输出日志

我们的目的是为了更方便的在日志中查看sql执行情况

详细实现步骤:只需要创建一个配置类即可


类定义与注解

java 复制代码
@Component
@Intercepts({
    @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
    @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
    @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
public class MybatisLog implements Interceptor {

使用 @Component 将该类注册为 Spring 组件。

使用 @Intercepts 注解定义拦截规则,指定拦截 StatementHandler 的 query、update 和 batch 方法。

实现 Interceptor 接口,重写 intercept 方法以自定义拦截逻辑。


初始化日志记录器

java 复制代码
private static final Logger log = LoggerFactory.getLogger(MybatisLog.class);

创建一个 SLF4J 日志记录器,用于后续输出 SQL 执行相关信息

拦截方法入口


java 复制代码
@Override
public Object intercept(Invocation invocation) throws Throwable {

重写 intercept 方法,这是插件的核心逻辑入口。

参数 Invocation 包含了被拦截方法的所有信息(目标对象、方法名、参数等)。


获取目标对象和开始时间

java 复制代码
Object target = invocation.getTarget();
long startTime = System.currentTimeMillis();

invocation.getTarget():获取当前被拦截的目标对象(这里是 StatementHandler)。

System.currentTimeMillis():记录 SQL 执行开始时间,用于计算耗时。


类型转换及异常处理

java 复制代码
StatementHandler statementHandler = (StatementHandler) target;
try {
    return invocation.proceed();
} finally {
    ...
}

将目标对象强制转换为 StatementHandler,以便访问其内部方法。

使用 try-finally 结构确保无论是否发生异常,都会执行日志记录逻辑。


记录结束时间和计算耗时

java 复制代码
long endTime = System.currentTimeMillis();
long sqlCost = endTime - startTime;

提取sql相关信息

java 复制代码
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

getBoundSql():获取 BoundSql 对象,包含原始 SQL 和参数信息。

getSql():提取原始 SQL 语句。

getParameterObject():获取传递给 SQL 的参数对象(可能是实体类或 Map)。

getParameterMappings():获取参数映射列表,描述每个占位符(如 ?)对应的参数信息。


输出日志

java 复制代码
log.info("参数:[{}]", parameterObject);
log.info("参数映射:[{}]", parameterMappings);
log.info("SQL: [{}] 执行耗时:[{} ms]", sql, sqlCost);

完整代码:

java 复制代码
package org.system.orderService.config;


import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.sql.Statement;
import java.util.List;

@Component
@Intercepts({
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
public class MybatisLog implements Interceptor {

    private static final Logger log = LoggerFactory.getLogger(MybatisLog.class);// 日志记录器


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
//        1. invocation.getTarget() 获取当前被拦截的目标对象
        Object target = invocation.getTarget();

//        2. 获取当前时间
        long startTime = System.currentTimeMillis();


//        3. 从StatementHandler中提取原始SQL、参数对象及参数映射列表
        StatementHandler statementHandler = (StatementHandler) target;

        try{
//           4. 拦截目标方法并执行
           return invocation.proceed();
        }finally {
            long endTime = System.currentTimeMillis();

//          5.  sql 执行耗时
            long sqlCost = endTime - startTime;

//          6.  获取原始SQL
            BoundSql boundSql = statementHandler.getBoundSql();
//             获取参数映射列表   SELECT * FROM user WHERE id = ? AND name = ?
            String sql = boundSql.getSql();
//            获取参数对象
            /*
            *  如果是对象
            *  User user = new User();
            *  user.setId(1);
            *  user.setName("Alice");
            * */

            /**
             * 如果Map
             *   Map<String, Object> params = new HashMap<>();
             *   params.put("id", 1);
             *   params.put("name", "Alice");
             */
            Object parameterObject = boundSql.getParameterObject();
//            获取参数映射列表
            /**
             *  ? 占位符的参数列表
             *   [
             *       ParameterMapping{property='id', mode=IN, javaType=int, jdbcType=INTEGER},
             *       ParameterMapping{property='name', mode=IN, javaType=string, jdbcType=VARCHAR}
             *   ]
             */
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();


//           7. 格式化sql语句

//            8. 输出日志
            log.info("参数:[{}]",parameterObject);
            log.info("参数映射:[{}]",parameterMappings);
            log.info("SQL: [{}] 执行耗时:[{} ms]",sql,sqlCost );

        }

    }

}
相关推荐
老王以为5 分钟前
前端视角下的 Java
java·javascript·程序员
看腻了那片水13 分钟前
开源一个对业务代码零侵入的透明数据治理框架 —— 【sangsang】
java·mybatis
Nyarlathotep011322 分钟前
JUC工具(3):StampedLock的基础和原理
java·后端
呱牛do it42 分钟前
企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 7)
java·vue
NE_STOP1 小时前
Redis--SDS字符串与集合的底层实现原理
java
直奔標竿1 小时前
Java开发者AI转型第二十二课!Spring AI 个人知识库实战(一)——架构搭建与核心契约落地
java·人工智能·后端·spring·架构
身如柳絮随风扬1 小时前
深入理解Java IO与NIO的区别:从BIO到NIO的演进
java·nio
A-Jie-Y1 小时前
JAVA设计模式-抽象工厂模式
java·设计模式
@insist1232 小时前
信息安全工程师-密码学专题(下):构建可信网络空间的核心机制
java·大数据·密码学·软考·信息安全工程师·软件水平考试
摇滚侠2 小时前
Java 零基础全套视频教程,面向对象(高级),笔记 105-120
java·开发语言·笔记