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

        }

    }

}
相关推荐
码云数智-大飞2 小时前
像写 SQL 一样搜索:dbVisitor 如何用 MyBatis 范式颠覆 ElasticSearch 开发
sql·elasticsearch·mybatis
让我上个超影吧2 小时前
【力扣34】在排序数组中查找元素的第一个和最后一个位置
java·数据结构·算法·leetcode
逍遥德2 小时前
Maven教程.04-如何阅读Maven项目
java·maven
xiaoliuliu123452 小时前
treeNMS-1.7.5部署步骤详解(附Java环境准备与数据库配置)
java·开发语言·数据库
没有bug.的程序员2 小时前
订单系统重构史诗:从单体巨兽到微服务矩阵的演进、数据一致性内核与分布式事务
java·微服务·矩阵·重构·分布式事务·数据一致性·订单系统
m0_738120722 小时前
应急响应——Solar月赛emergency靶场溯源过程(内含靶机下载以及流量分析)
java·开发语言·网络·redis·web安全·系统安全
逍遥德2 小时前
Maven教程.03-如何阅读pom.xml文件
xml·java·后端·maven
上海合宙LuatOS2 小时前
LuatOS核心库API——【json 】json 生成和解析库
java·前端·网络·单片机·嵌入式硬件·物联网·json
没有bug.的程序员2 小时前
金融风控系统:实时规则引擎内核、决策树物理建模与 Drools 性能压榨
java·数据库·决策树·金融·drools·物理建模·实时规则