自定义mybatis插件实现sql日志打印

news2024/10/6 2:22:38

自定义mybatis插件实现sql日志打印

mysql插件实现原理

官网的关键信息

参考文档

https://mybatis.org/mybatis-3/zh/configuration.html#plugins

官方文档

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

插件运行源码流程

mybatis 在 配置文件里面设置 plug , 所以 configuration 可以添加插件

Configuration.java

image-20221028214701391

  • Executor
  • ParameterHandler
  • ResultSetHandler
  • StatementHandler

通过查看源码 , 知道插件增强的是 mybatis 以上的几个对象

interceptorChain

interceptorChain.pluginAll(…)方法

mybatis 中的 InterceptorChain 类似于 springmvc 中的 拦截器链

image-20221028215002987

Interceptor

image-20221028215148909

实现 InvocationHandler , 我们知道 Plugin 类使用了 JDK 动态代理

最后执行插件方法 , 调用的是该类的 invoke 方法

image-20221028215225908

image-20221028215400859

inovke方法

image-20221028215651547

参考pagehelper

image-20221028215810987

实现sql日志打印插件

前置环境

springboot + mybatis + mysql

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.3</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.4</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

实现 Interceptor 接口

package com.example.plug;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Properties;

/**
 * 1.可以用来分析SQL执行效率
 * 2.可以用来获取实际执行的SQL
 */

@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}
)
@Slf4j
public class SqlInterceptor implements Interceptor {
//    private static final Log log = LogFactory.getLog(SqlInterceptor.class);    
    /**
     * 最小打印时间 sql时间超过这个值才打印日志 毫秒
     **/
    private int MIN_SIZE = 0;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = null;
        if (invocation.getArgs().length > 1) {
            parameter = invocation.getArgs()[1];
        }
        String sqlId = mappedStatement.getId();
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        Configuration configuration = mappedStatement.getConfiguration();

        long startTime = System.currentTimeMillis();
        Object result = null;
        try {
            result = invocation.proceed();
        } finally {
            try {
                long sqlCostTime = System.currentTimeMillis() - startTime;
                String sql = getSql(configuration, boundSql);
                formatSqlLog(mappedStatement.getSqlCommandType(), sqlId, sql, sqlCostTime, result);
            } catch (Exception ignored) {
                log.error("SQL插件执行失败 Mapper:{} 参数对象:{}", sqlId, JSON.toJSONString(boundSql.getParameterObject()), ignored);
            }
        }
        return result;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        if (properties == null) {
            return;
        }
        if (properties.containsKey("minLogSize")) {
            MIN_SIZE = Integer.valueOf(properties.getProperty("minLogSize"));
        }
    }

    private String getSql(Configuration configuration, BoundSql boundSql) {
        // 输入sql字符串空判断
        String sql = boundSql.getSql();
        if (StrUtil.isBlank(sql)) {
            return "";
        }

        //去掉换行符
        sql = sql.replaceAll("[\\s\n ]+", " ");

        //填充占位符, 目前基本不用mybatis存储过程调用,故此处不做考虑
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (!parameterMappings.isEmpty() && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = this.replacePlaceholder(sql, parameterObject);
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = replacePlaceholder(sql, obj);
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = replacePlaceholder(sql, obj);
                    }
                }
            }
        }
        return sql;
    }

    private String replacePlaceholder(String sql, Object parameterObject) {
        String result;
        if (parameterObject == null) {
            result = "NULL";
        } else if (parameterObject instanceof String) {
            result = String.format("'%s'", parameterObject.toString());
        } else if (parameterObject instanceof Date) {
            result = String.format("'%s'", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(parameterObject));
        } else {
            result = parameterObject.toString();
        }
        return sql.replaceFirst("\\?", result);
    }

    private void formatSqlLog(SqlCommandType sqlCommandType, String sqlId, String sql, long costTime, Object obj) {
        if (costTime > MIN_SIZE) {
            if (sqlCommandType == SqlCommandType.UPDATE || sqlCommandType == SqlCommandType.INSERT || sqlCommandType == SqlCommandType.DELETE) {
                log.info("[{}ms] [{}] {}; 影响行数:{}", costTime, sqlId, sql, obj);
            }
            if (sqlCommandType == SqlCommandType.SELECT) {
                log.info("[{}ms] [{}] {}; 结果行数:{}", costTime, sqlId, sql, ((Collection<?>) obj).size());
            }
        }
    }
}

mybatis 添加该插件

SpringBoot 中 SqlSessionFactory 是单例的

通过 SqlSessionFactory 得到 Configuration

configuration 的 addInterceptor 方法添加插件

@Configuration
public class MybatisPlugConfig implements SmartInitializingSingleton {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public void afterSingletonsInstantiated() {
        sqlSessionFactory.getConfiguration().addInterceptor(new SqlInterceptor());
    }
}

测试

测试方便 , 省略service

controller

@RestController
@RequestMapping("/test")
public class HelloController {


    @Autowired
    private PersonDao personDao;


    @GetMapping("/ssm")
    public List<PersonEntity> ssm() {
        return personDao.list();
    }
    
    @GetMapping("/insert")
    public String insert(){
        PersonEntity personEntity = new PersonEntity();
        personEntity.setPid(null);
        personEntity.setPname(UUID.randomUUID().toString().substring(8).toString());
        personDao.insert(personEntity);
        return "插入成功";
    }

    @GetMapping("/select")
    public List<PersonEntity> select(Integer pid , String pname){
        return personDao.selectByParam(pid , pname);
    }
}

dao

@Mapper
public interface PersonDao  {


     @Select("select * from person")
     List<PersonEntity> list();
     
     int insert(PersonEntity personEntity);

     List<PersonEntity> selectByParam(@Param("pid") Integer pid, @Param("pname") String pname);
}

控制台打印

2022-10-28 22:07:18.913 INFO 26316 — [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet ‘dispatcherServlet’
2022-10-28 22:07:18.913 INFO 26316 — [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet ‘dispatcherServlet’
2022-10-28 22:07:18.917 INFO 26316 — [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms
2022-10-28 22:07:27.247 INFO 26316 — [nio-8080-exec-6] com.example.plug.SqlInterceptor : [48ms] [com.example.dao.PersonDao.list] select * from person; 结果行数:5
2022-10-28 22:07:30.759 INFO 26316 — [nio-8080-exec-8] com.example.plug.SqlInterceptor : [34ms] [com.example.dao.PersonDao.selectByParam] select * from person WHERE pname like concat(‘%’,concat(‘1’,‘%’)) and pid = 1; 结果行数:0
2022-10-28 22:07:32.936 INFO 26316 — [nio-8080-exec-9] com.example.plug.SqlInterceptor : [31ms] [com.example.dao.PersonDao.selectByParam] select * from person WHERE pname like concat(‘%’,concat(‘1’,‘%’)); 结果行数:2
2022-10-28 22:07:36.464 INFO 26316 — [io-8080-exec-10] com.example.plug.SqlInterceptor : [34ms] [com.example.dao.PersonDao.selectByParam] select * from person WHERE pid = 1; 结果行数:1
2022-10-28 22:07:38.605 INFO 26316 — [nio-8080-exec-1] com.example.plug.SqlInterceptor : [33ms] [com.example.dao.PersonDao.selectByParam] select * from person; 结果行数:5
oncat(‘1’,‘%’)); 结果行数:2
2022-10-28 22:07:36.464 INFO 26316 — [io-8080-exec-10] com.example.plug.SqlInterceptor : [34ms] [com.example.dao.PersonDao.selectByParam] select * from person WHERE pid = 1; 结果行数:1
2022-10-28 22:07:38.605 INFO 26316 — [nio-8080-exec-1] com.example.plug.SqlInterceptor : [33ms] [com.example.dao.PersonDao.selectByParam] select * from person; 结果行数:5
2022-10-28 22:07:45.440 INFO 26316 — [nio-8080-exec-2] com.example.plug.SqlInterceptor : [53ms] [com.example.dao.PersonDao.insert] INSERT INTO person ( pid, pname ) VALUES ( 6, ‘-9bac-472f-a95c-9d97b11ab0e1’ ); 影响行数:1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1698.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaSE】一篇文章领悟Java运算符

前言&#xff1a; 作者简介&#xff1a;爱吃大白菜1132 人生格言:纸上得来终觉浅&#xff0c;绝知此事要躬行 如果文章知识点有错误的地方不吝赐教&#xff0c;和大家一起学习&#xff0c;一起进步&#xff01; 如果觉得博主文章还不错的话&#xff0c;希望三连支持&#xff01…

Java项目:超市管理系统(java+SSM+JSP+LayUI+jQ+Mysql)

源码获取&#xff1a;俺的博客首页 "资源" 里下载&#xff01; 项目介绍 本项目分为超级管理员、总经理、店长、员工等角色&#xff0c;超级管理员可添加修改删除角色并进行角色菜单配置&#xff1b; 超级管理员角色包含以下功能&#xff1a; 商品管理&#xff1a;添…

C语言高级-4栈

14天阅读挑战赛 目录 一、栈的原理 1、栈的定义 2、栈的应用 &#xff08;1&#xff09;选课问题 &#xff08;2&#xff09;旅游&#xff1a;怎么样把每个城市去且仅去一遍&#xff1f; &#xff08;3&#xff09;栈的使用场景 &#xff08;4&#xff09;思考&#xf…

C++多态之虚函数表详解及代码示例

引言 C相对其他面向对象语言来说&#xff0c;之所以灵活、高效。很大程度的占比在于其多态技术和模板技术。C虚函数表是支撑C多态的重要技术&#xff0c;它是C动态绑定技术的核心。 如果对多态还不了解的小伙伴&#xff0c;可以点这里C多态详解基础篇。 在不考虑继承的情况下…

Vue3 Hooks 模块化抽离

Vue3中的Hooks 其实就是业务逻辑的抽离&#xff0c;跟Vue2中mixin 本质上是一样的&#xff1a;将当前组件的业务逻辑抽离到一个公共的文件中&#xff0c;提高逻辑复用性&#xff0c;让当前组件看起来更加清爽&#xff0c;不太一样的地方是我们封装hooks 的时候一般是返回一个函…

如何不改动 GatewayWorker 依赖包下自定义协议

前言&#xff1a; GatewayWorker 是 Workerman 的一个框架&#xff0c;对应用层开发者更友好。GatewayWorker 多了一个网关&#xff0c;也就是 Gateway&#xff0c;负责与客户端连接&#xff0c;消息转发等。而自定义的协议&#xff0c;也就是 gateway 面向客户端提供服务的协议…

java毕业设计——基于java+JDBC+sqlserver的物业管理系统设计与实现(毕业论文+程序源码)——物业管理系统

基于javaJDBCsqlserver的物业管理系统设计与实现&#xff08;毕业论文程序源码&#xff09; 大家好&#xff0c;今天给大家介绍基于javaJDBCsqlserver的物业管理系统设计与实现&#xff0c;文章末尾附有本毕业设计的论文和源码下载地址哦。 文章目录&#xff1a; 基于javaJDB…

【H5微信授权】简单实现H5页面微信授权功能,微信开发者工具报错 系统错误,错误码-1,undefined解决办法【详细】

前言 最近写到了H5公众号&#xff0c;需要微信授权的功能。 这里记录一下授权的流程和踩了个坑 图片 授权代码执行后会跳转到授权的地方&#xff0c;没有授权的会有确认授权&#xff0c;授权过得会这样&#xff0c;直接自动登录&#xff0c;然后再跳转到中转页 授权流程 …

204 - 205.表的基本用法

表的基本操作 1.基本概念 1.1数据库和表的关系 每个数据库包含N张表&#xff0c;及表示在库中 1.2 表&#xff08;二维表&#xff09; 行和列组成&#xff0c;可以将复杂的数据保存在简单的表中 表中的每一行就是一条完整的记录 表中的列用于保存每条记录中特点的信息 2.…

【Redis】散列表(Hash)和列表(List)的运用和理解以及Hash和List应用场景对比详解

文章目录一. 散列表(hash)1.1 基本操作1.2 当value字符串的内容是数字时二.列表&#xff08;List&#xff09;2.1 基本操作三.Hash和List的应用场景3.1Hash的应用场景3.2List的应用场景一. 散列表(hash) Redis哈希是字符串类型字段和值的映射表。哈希特别适合存储对象。 Redis中…

mindspore.dataset的map问题

1、创建一个包含transform的自定义类并实例化 2. 类的实现如下&#xff1a; 请注意&#xff1a;在call函数中&#xff0c;我并没有调用init中定义的transform操作。 3. ImageFolder_forPretrain的定义如下。 在划红线的那行&#xff0c;出现错误。 错误信息&#xff1a; 疑问…

【Spark】spark-submit作业提交及参数设定

note 文章目录note一、Spark的常用启动方式1.1 local本地模式1.2 Standalone模式1.3 Spark on Yarn模式二、spark-submit 详细参数说明--master--deploy-mode--class--name--jars--packages--exclude-packages--repositories--py-files--files--conf PROPVALUE--properties-fil…

【趣学算法】Day3 贪心算法——背包问题

14天阅读挑战赛努力是为了不平庸~ 算法学习有些时候是枯燥的&#xff0c;这一次&#xff0c;让我们先人一步&#xff0c;趣学算法&#xff01; ❤️一名热爱Java的大一学生&#xff0c;希望与各位大佬共同学习进步❤️ &#x1f9d1;个人主页&#xff1a;周小末天天开心 各位大…

tomcat应用部署

目录 tomcta介绍 tomcat安装 每个目录的作用 端口 实验开始 安装&#xff0c;启动MySQL 创建数据库 更改数据库连接 项目导入tomcat和数据库查看 重启tomcat 访问应用 【注意】 tomcta介绍 Tomcat的是完全开源的 Tomcat的是免费的 Tomcat不支持EJB 应用范围&#xff1…

创造一个表格编辑距离指标

这个是我自研的&#xff0c; 与百度PaddleOCR的方式略有不同。 数据的格式&#xff1a; 相当于一个目标检测有两类&#xff0c;分别是table和cell。 在预测值和标签中要先把根据位置关系所有的cell划分到不同的table中。 另外cell标签中还有起止位置 比如 四个数字代表行和…

window10远程桌面控制Ubuntu系统

Windows操作系统作为全球使用最多的个人操作系统&#xff0c;在我们身边随处可见&#xff0c;但放眼各类电子设备的操作系统&#xff0c;windows并不是一家独大&#xff0c;服务器系统大多基于Linux系统开发、手机操作系统几乎都是安卓、更不用说还有苹果的iOS、树莓派、Ubuntu…

【MySQL高级篇】数据库到底是什么?一文带你快速上手MySQL

在学习JavaWeb过程中&#xff0c;数据库学习是不可或缺的。整个JavaWeb体系中&#xff0c;数据库部分用于储存和管理数据&#xff0c;而数据作为网页中非常重要的一部分&#xff0c;自然我们是有必要深入学习数据库的。 推荐学习专栏&#xff1a;Java编程 进阶之路 文章目录1. …

【C语言 数据结构】顺序表的使用

本文借鉴点击跳转 上一篇&#xff1a;线性表的简绍 文章目录顺序表什么是顺序表顺序表的初始化顺序表插入元素顺序表删除元素顺序表 什么是顺序表 顺序表又称顺序存储结构&#xff0c;是线性表的一种&#xff0c;专门存储逻辑关系为“一对一”的数据。 顺序表存储数据的具体…

Linux~一些基本开发工具的使用(yum,vim,gcc,gdb,makefile)

目录 一.yum——安装软件 二.Vim——文本编辑器 &#xff08;1&#xff09;.命令模式 &#xff08;2&#xff09;.底行模式 &#xff08;3&#xff09;.插入模式 tips:给对应用户配置sudo命令 一些注意事项 三.gcc/g——编译器 &#xff08;1&#xff09;.gcc如何完成…

客快物流大数据项目(八十三):Kudu的优化

文章目录 Kudu的优化 一、​​​​​​​Kudu关键配置 二、​​​​​​​​​​​​​​Kudu的使用限制 1、​​​​​​​​​​​​​​主键 2、Cells 3、​​​​​​​字段 4、表 5、其他限制 6、​​​​​​​​​​​​​​分区限制 7、扩展建议和限制 8、​…