写一个MyBatis的plugins,记录完整SQL日志

server/2024/10/21 3:21:04/

目录

  • 概览
  • MyBatis Plugins
    • Invocation
    • MappedStatement
    • Configuration
    • BoundSql
    • MetaObject
  • 完整示例
  • 参考资料

概览

    MyBatis的SQL输出日志语句和参数是分开打印的,我们调试时需要将语句和参数再次拼接才能进行测试执行。当遇到长SQL和参数较多时,还原一个SQL往往需要十几分钟的时间。有很多优秀(懒)的程序员通过编写集成开发环境插件的方式来优化SQL还原拼接的过程,例如Idea平台下的: mybatis-log-plugin(收费)、mybatis-log-free(免费)。

    最近接到的项目有很多长SQL并且参数的数量也很多,在做SQL优化时需要花很多时间在SQL拼接上,想到前同事使用MyBatis框架插件机制来打印完整的SQL日志,尝试自己实现一下,同时也是为了加强对MyBatis框架的了解。

MyBatis Plugins

    MyBatis的插件机制更准确的说是拦截器,可以用来拦截执行SQL语句执行之前或之后的操作,从而实现对 SQL 执行过程的干预。例如:参数加解密、权限判断、统一字段处理、添加分页、记录SQL执行时间等功能。实现MyBatis插件非常简单,写一个实现Interceptor接口的类,并通过@Intercepts、@Signature注解描述自己拦截的行为即可。

示例-1

import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Signature;// 拦截所有的 update 方法调用
@Intercepts({@Signature(type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})
})
public class ExamplePlugin implements Interceptor {private Properties properties = new Properties();@Overridepublic Object intercept(Invocation invocation) throws Throwable {// TODO 执行之前的业务处理(自定义)Object returnObject = invocation.proceed();// TODO 执行之后的业务处理(自定义)return returnObject;}/*** 设置插件参数,内容可以在mybatis-config.xml中设置*/@Overridepublic void setProperties(Properties properties) {this.properties = properties;}
}

Invocation

    Invocation象包含了被拦截方法的所有必要信息,Target(被调用的对象),Method(被调用的方法),Arguments(传递给方法的参数列表)。proceed 方法用于继续执行被拦截的方法。如果不调用 proceed 方法,被拦截的方法将不会被执行。

MappedStatement

    MappedStatement是MyBatis中的一个核心概念和内部类,它是MyBatis执行SQL语句的关键对象。每个MappedStatement对象代表了一个已经映射的SQL语句,包括了执行此SQL语句所需的所有信息,如SQL语句文本、参数映射、结果映射、缓存策略等
    MappedStatement对象由MyBatis的内部机制自动创建并管理的。当用户定义了一个Mapper接口中的方法或者XML映射文件中的SQL语句时,MyBatis会根据这些定义生成相应的MappedStatement对象,并将其存储在Configuration对象的内部映射表中。

Configuration

    Configuration创建和管理MappedStatement对象,是MyBatis的核心配置对象,包含了所有映射信息、类型别名、类型处理器、对象工厂等配置。

BoundSql

    BoundSql代表一个已绑定参数的SQL语句。由SqlSource创建,SqlSource是MyBatis中用于解析SQL语句并创建BoundSql的抽象类,当执行一个MappedStatement时,MyBatis会根据MappedStatement中的SqlSource创建一个BoundSql实例。BoundSql包含了SQL语句文本、参数列表以及参数映射等信息。

MetaObject

    MetaObject是一个重要工具类,它主要用于简化JavaBean的操作,提供了一种统一的方式来访问和操作对象的属性。

完整示例

import org.apache.commons.collections4.CollectionUtils;
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.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
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.DateFormat;
import java.util.*;
import java.util.regex.Matcher;@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MyBatisLogPlugins implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {long start = System.currentTimeMillis();try {return invocation.proceed();} finally {long end = System.currentTimeMillis();printSql(invocation, start, end);}}@Overridepublic Object plugin(Object target) {return Interceptor.super.plugin(target);}@Overridepublic void setProperties(Properties properties) {Interceptor.super.setProperties(properties);}/*** 打印完整SQL和执行耗时** @param invocation* @param start* @param end*/private void printSql(Invocation invocation, long start, long end) {// 获取参数Object parameter = null;if (invocation.getArgs().length > 1) {parameter = invocation.getArgs()[1];}// 获取最终的SQLMappedStatement ms = (MappedStatement) invocation.getArgs()[0];BoundSql boundSql = ms.getBoundSql(parameter);// 获取节点的配置Configuration configuration = ms.getConfiguration();// 替换SQL中的多余的空格String sql = boundSql.getSql().replaceAll("[\\s]+", " ");// 获取参数Object parameterObject = boundSql.getParameterObject();List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {// String、Date等基本类型的处理sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(adornParamValue(parameterObject)));} else {// JavaBean、Collection、Map等复杂入参对象MetaObject metaObject = configuration.newMetaObject(parameterObject);for (ParameterMapping parameterMapping : parameterMappings) {String propertyName = parameterMapping.getProperty();if (metaObject.hasGetter(propertyName)) {Object obj = metaObject.getValue(propertyName);sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(adornParamValue(obj)));} else if (boundSql.hasAdditionalParameter(propertyName)) {// 该分支是动态sqlObject obj = boundSql.getAdditionalParameter(propertyName);sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(adornParamValue(obj)));} else {//打印出缺失,提醒该参数缺失并防止错位sql = sql.replaceFirst("\\?", "missing");}}}}Object target = invocation.getTarget();System.out.println(ms.getId() + " -> 耗时:" + (end - start) + " -> 完整SQL:" + sql);}/*** 装饰参数值** @param obj* @return*/private static String adornParamValue(Object obj) {String value;if (obj instanceof String) {value = "'" + obj.toString() + "'";} else if (obj instanceof Date) {DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);value = "'" + formatter.format(new Date()) + "'";} else {if (obj != null) {value = obj.toString();} else {value = "";}}return value;}}

参考资料

  • mybatis-3 plugins
  • 风一样的码农 - 博客

http://www.ppmy.cn/server/95831.html

相关文章

C语言基础题:硬币问题(C语言版)

1.题目描述 今有面值为 1、5、11 元的硬币各无限枚。 想要凑出 九 元&#xff0c;问需要的最少硬币数量。 2.输入格式 仅一行&#xff0c;一个正整数 n。 3.输出格式 仅一行&#xff0c;一个正整数&#xff0c;表示需要的硬币个数。 4.输入输出样例 1.输入1&#xff1a; 15 2…

Spring Boot集成RabbitMQ的使用

&#x1f341; 作者&#xff1a;知识浅谈&#xff0c;CSDN签约讲师&#xff0c;CSDN博客专家&#xff0c;华为云云享专家&#xff0c;阿里云专家博主 &#x1f4cc; 擅长领域&#xff1a;全栈工程师、爬虫、ACM算法 &#x1f525; 微信&#xff1a;zsqtcyw 联系我领取学习资料 …

HTTP/2:让网络飞起来

文章目录 一、HTTP/2 的基本概念和背景二、HTTP/2 的主要特性和优势2.1 二进制帧2.2 多路复用2.3 头部压缩2.4 服务器推送 三、HTTP/2 的实现和部署四、HTTP/2 与现有技术的比较五、HTTP/2 与 Web 性能优化六、结束语&#xff1a;让 HTTP/2 助力你的 Web 开发 今天我们来聊聊一…

C# 组合CancellationTokenSource的使用

前言 在异步编程中&#xff0c;经常需要使用CancellationToken来取消任务的执行。 但是通常情况下&#xff0c;一个耗时任务还需要有超时机制。那个如何让一个任务既可以超市自动取消也可以手动取消&#xff1f; 组合CancellationTokenSourced的使用 //创建两个cts(一个手动…

【必看!】阿里云推出QWen-7B和QWen-7b-Chat,开放免费商用!

阿里云于8月3日宣布开源两款重要的大型模型——QWen-7B和QWen-7b-Chat。这两款模型的参数规模达到了令人瞩目的70亿&#xff0c;并且已经在Hugging Face和ModelScope平台上开放&#xff0c;并可免费商用。以下是相关链接&#xff1a; GitHub项目主页&#xff1a;https://githu…

MySQL索引及索引的优化策略

1.什么是索引&#xff1f; 索引是对数据库表中一列或多列的值进行排序的一种结构&#xff0c;使用索引可快速访问数据库表中的特定信息 2.为什么使用索引&#xff1a; 1.高效性&#xff1a;利用索引可以提高数据库的查询效率 2.唯一性&#xff1a;索引可以确保所查的数据的唯一…

高等数学精解【3】

文章目录 线性方程组齐次线性方程组高阶行列式 参考文献 线性方程组 齐次线性方程组 含有两个三元齐次线性方程的方程组 两个三元齐次线性方程通常指的是形如&#xff1a; a 1 x b 1 y c 1 z 0 a 2 x b 2 y c 2 z 0 a_1x b_1y c_1z 0 \\a_2x b_2y c_2z 0 a1​xb…

力扣1202.交换字符串中的元素

力扣1202.交换字符串中的元素 并查集 multiset&#xff1a;允许重复&#xff0c;并自动排序 class Solution {vector<int> vec;public:string smallestStringWithSwaps(string s, vector<vector<int>>& pairs) {unordered_map<int,multiset<cha…