实现一个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 );

        }

    }

}
相关推荐
二月夜7 小时前
剖析Java正则表达式回溯问题
java·正则表达式
xuhaoyu_cpp_java7 小时前
项目学习(三)分页查询
java·经验分享·笔记·学习
程序员二叉8 小时前
【Java】集合面试全套精讲|HashMap/ArrayList高频考点完整版
java·面试·哈希算法
cfm_29148 小时前
JVM GC垃圾回收初步了解
java·开发语言·jvm
心之伊始8 小时前
LangChain4j RAG 实战:Java 后端如何把本地文档接入 Embedding 检索链路
java·架构·源码分析·csdn
许彰午9 小时前
17_synchronized关键字深度解析
java·开发语言
Xzh042310 小时前
AI Agent 学习路线(Java 后端方向)
java·人工智能·学习
艾利克斯冰11 小时前
Java 设计模式-行为型模式(更新中)
java·开发语言·设计模式
倒霉蛋小马11 小时前
Java新特性:record关键字
java·开发语言
折哥的程序人生 · 物流技术专研11 小时前
《Java 100 天进阶之路》第95篇:消息队列基础(RocketMQ/Kafka)(2026版)
java·面试·kafka·rocketmq·java-rocketmq·求职招聘