实现一个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 小时前
新人笔记---ApiFox的一些常见使用出错
java·笔记·spring
栗子~~7 小时前
JAVA - 二层缓存设计(本地缓冲+redis缓冲+广播所有本地缓冲失效) demo
java·redis·缓存
YDS8298 小时前
DeepSeek RAG&MCP + Agent智能体项目 —— RAG知识库的搭建和接口实现
java·ai·springboot·agent·rag·deepseek
未若君雅裁9 小时前
MyBatis 一级缓存、二级缓存与清理机制
java·缓存·mybatis
AI人工智能+电脑小能手9 小时前
【大白话说Java面试题 第65题】【JVM篇】第25题:谈谈对 OOM 的认识
java·开发语言·jvm
阿维的博客日记10 小时前
Nacos 为什么能让配置动态生效?(涉及 @RefreshScope 注解)
java·spring
雨辰AI10 小时前
SpringBoot3 + 人大金仓读写分离 + 分库分表 + 集群高可用 全栈实战
java·数据库·mysql·政务
常常有10 小时前
MySQL 底层执行原理:输入SQL语句到两阶段提交
数据库·sql·mysql
辰海Coding11 小时前
MiniSpring框架学习-完成的 IoC 容器
java·spring boot·学习·架构
小小编程路11 小时前
C++ 多线程与并发
java·jvm·c++