使用MybatisPlus實現sql日志打印優化

背景:

在排查無憂行后臺服務日志時,一個請求可能會包含多個執行的sql,經常會遇到SQL語句與對應參數不連續顯示,或者參數較多需要逐個匹配的情況。這種情況下,如果需要還原完整SQL語句就會比較耗時。因此,我希望能優化這一流程。

正文:

在平時排查sql執行情況時,對于sql查詢參數比較多或者多個sql在同一個請求下執行的時候會比較麻煩,需要查看每個參數的值一一對應檢查是否有異常,有時候還需要將查詢sql的問號還原去庫里面查詢結果,就類似下面的日志:
在這里插入圖片描述

一個一個參數還原比較麻煩,因此針對這種問題,我找到了一個解決辦法,重寫MybatisPlus的InnerInterceptor,將sql日志重新組裝,那么在查看sql日志時候就可以直接查看賦值好參數的sql了,將上面截圖的sql整合之后可以轉化為下面的sql:
在這里插入圖片描述

具體的思路以及實現方案,以無憂行項目為例

實現思路,利用mybatisplus提供的接口InnerInterceptor,重寫sql日志,并將重寫之后的類注入到sqlSessionFactory中,在執行sql之前捕獲到并打印。
解釋一下InnerInterceptor類:InnerInterceptor 是 MyBatis-Plus 框架中的一個核心攔截器接口,它是 MyBatis 攔截器機制的擴展,用于在 SQL 執行過程中進行攔截和增強。
InnerInterceptor工作原理:InnerInterceptor 通過 MyBatis 的插件機制工作,在以下時機進行攔截:

- beforeQuery:查詢操作執行前
- beforeUpdate:更新操作執行前
- beforePrepare:SQL 準備階段

針對查詢sql執行的sql重整,使用的是beforeQuery,針對更新sql執行的重整,使用的是beforeUpdate,以下是具體實現:

1.首先,添加依賴,

maven依賴:

<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.0</version>
</dependency>

或者gradle依賴:

compile("com.baomidou:mybatis-plus-generator:3.4.0")

2.重寫InnerInterceptor

package com.cmi.jego.micro.flight.admin.service.config;import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.extern.slf4j.Slf4j;
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.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.sql.SQLException;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;/*** sql日志重寫** @author zhouxy* @date 2025年05月08日 11:27*/
@Slf4j
@org.springframework.context.annotation.Configuration
public class MybatisPlusLogRewrite implements InnerInterceptor {@Overridepublic void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {log.info("beforeQuery");logInfo(boundSql, ms, parameter);}@Overridepublic void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {log.info("beforeUpdate");BoundSql boundSql = ms.getBoundSql(parameter);logInfo(boundSql, ms, parameter);}private static void logInfo(BoundSql boundSql, MappedStatement ms, Object parameter) {try {// 獲取到節點的id,即sql語句的idString sqlId = ms.getId();// 獲取節點的配置Configuration configuration = ms.getConfiguration();// 獲取到最終的sql語句String sql = getSql(configuration, boundSql, sqlId);log.info("完整的sql:{}", sql);} catch (Exception e) {log.error("異常:{}", e.getLocalizedMessage(), e);}}// 封裝了一下sql語句,使得結果返回完整xml路徑下的sql語句節點id + sql語句public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId) {return sqlId + ":" + showSql(configuration, boundSql);}// 進行?的替換public static String showSql(Configuration configuration, BoundSql boundSql) {// 獲取參數Object parameterObject = boundSql.getParameterObject();List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
//        log.info("boundSql:{}",boundSql.getSql());// sql語句中多個空格都用一個空格代替String sql = boundSql.getSql().replaceAll("[\\s]+", " ");if (CollectionUtils.isNotEmpty(parameterMappings) && parameterObject != null) {// 獲取類型處理器注冊器,類型處理器的功能是進行java類型和數據庫類型的轉換TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();// 如果根據parameterObject.getClass()可以找到對應的類型,則替換if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {sql = sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(parameterObject)));} else {// MetaObject主要是封裝了originalObject對象,提供了get和set的方法用于獲取和設置originalObject的屬性值,主要支持對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(getParameterValue(obj)));} else if (boundSql.hasAdditionalParameter(propertyName)) {// 該分支是動態sqlObject obj = boundSql.getAdditionalParameter(propertyName);sql = sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(obj)));} else {// 打印出缺失,提醒該參數缺失并防止錯位sql = sql.replaceFirst("\\?", "缺失");}}}}return sql;}// 如果參數是String,則添加單引號, 如果是日期,則轉換為時間格式器并加單引號; 對參數是null和不是null的情況作了處理private static String getParameterValue(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;}
}

3.將當前的重寫類MybatisPlusLogRewrite注入到sqlSessionFactory中。

package com.cmi.jego.micro.flight.admin.service.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;import javax.annotation.PostConstruct;
import java.util.List;/*** @author zhouxy* @date 2025年05月09日 15:58*/
@Configuration
public class MyBatisPlusConfig {@Autowiredprivate List<SqlSessionFactory> sqlSessionFactoryList;@Autowiredprivate MybatisPlusLogRewrite myInnerInterceptor;/*** 添加Mybatis攔截器** @author zhouxy* @date 2025/5/15 16:29*/@PostConstructpublic void addMybatisInterceptor() {for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();//將sql攔截器添加到MybatisPlusInterceptor攔截器鏈MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(myInnerInterceptor);configuration.addInterceptor(mybatisPlusInterceptor);}}
}

關閉其他sql打印日志,執行查詢sql,只打印當前重寫之后的sql日志如下:

2025-05-14 18:03:01.353  INFO 34718 --- [           main] c.c.j.m.f.a.service.handler.LogHandler   : function:[OrderRefundServiceImpl.queryOrderListStatusCount], request: {"lang":"zh_CN","pageNum":1,"pageSize":10}
2025-05-14 18:03:01.385  INFO 34718 --- [           main] c.c.j.m.f.a.s.c.MybatisPlusLogRewrite    : beforeQuery
2025-05-14 18:03:01.387  INFO 34718 --- [           main] c.c.j.m.f.a.s.c.MybatisPlusLogRewrite    : 完整的sql:com.cmi.jego.micro.flight.admin.service.mapper.jegotrip.TicketRefundMapper.queryListStatusCount:select distinct(tr.id) as refundId, tr.status from ticket_refund tr left join ticket_order td on tr.order_id = td.order_id left join ticket_passenger tp on tr.order_id = tp.order_id left join ticket_order_detail tod on tr.order_id = tod.order_id WHERE ( td.search_channel in ( 'hbgj-api-domestic-custom' ) or (td.order_source is null and td.search_channel is null) or td.order_source = '0' )
2025-05-14 18:03:01.395  WARN 34718 --- [           main] c.a.druid.pool.DruidAbstractDataSource   : discard long time none received connection. , jdbcUrl : jdbc:mysql://localhost:3306/ticket_jegotrip?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8, version : 1.2.5, lastPacketReceivedIdleMillis : 263261
2025-05-14 18:03:01.395  WARN 34718 --- [           main] c.a.druid.pool.DruidAbstractDataSource   : discard long time none received connection. , jdbcUrl : jdbc:mysql://localhost:3306/ticket_jegotrip?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8, version : 1.2.5, lastPacketReceivedIdleMillis : 263912
2025-05-14 18:03:01.396  WARN 34718 --- [           main] c.a.druid.pool.DruidAbstractDataSource   : discard long time none received connection. , jdbcUrl : jdbc:mysql://localhost:3306/ticket_jegotrip?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8, version : 1.2.5, lastPacketReceivedIdleMillis : 263933
2025-05-14 18:03:01.396  WARN 34718 --- [           main] c.a.druid.pool.DruidAbstractDataSource   : discard long time none received connection. , jdbcUrl : jdbc:mysql://localhost:3306/ticket_jegotrip?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8, version : 1.2.5, lastPacketReceivedIdleMillis : 263958
2025-05-14 18:03:01.396  WARN 34718 --- [           main] c.a.druid.pool.DruidAbstractDataSource   : discard long time none received connection. , jdbcUrl : jdbc:mysql://localhost:3306/ticket_jegotrip?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8, version : 1.2.5, lastPacketReceivedIdleMillis : 263986
2025-05-14 18:03:01.851  INFO 34718 --- [           main] c.c.j.m.f.a.s.c.MybatisPlusLogRewrite    : beforeQuery
2025-05-14 18:03:01.851  INFO 34718 --- [           main] c.c.j.m.f.a.s.c.MybatisPlusLogRewrite    : 完整的sql:com.cmi.jego.micro.flight.admin.service.mapper.jegotrip.TicketRefundMoneyMapper.queryFailedByRefundIds:SELECT id , order_id, user_id, refund_id, pay_order_id, supplier_order_id, type, price, refund_price, status, refund_time, limit_time, refund_no, create_time, update_time, remark FROM ticket_refund_money WHERE refund_id IN ( 1295 , 1296 , 1423 , 1424 ) AND status = 4
2025-05-14 18:03:01.871  INFO 34718 --- [           main] c.c.j.m.f.a.service.handler.LogHandler   : class:[OrderRefundServiceImpl.queryOrderListStatusCount] response: {"bizCode":0,"bizMsg":"SUCCESS","data":{"statusCounts":[{"count":1,"status":-1,"statusDesc":"已取消"},{"count":2,"status":3,"statusDesc":"已退票退款"},{"count":1,"status":4,"statusDesc":"退票異常"}]},"rpcCode":0,"rpcMsg":"SUCCESS"}, cost: 521,
2025-05-14 18:03:01.876  INFO 34718 --- [           main] c.c.j.m.f.a.s.s.OrderRefundServiceTest   : {"bizCode":0,"bizMsg":"SUCCESS","data":{"statusCounts":[{"count":1,"status":-1,"statusDesc":"已取消"},{"count":2,"status":3,"statusDesc":"已退票退款"},{"count":1,"status":4,"statusDesc":"退票異常"}]},"rpcCode":0,"rpcMsg":"SUCCESS"}

性能評估方面

重寫InnerInterceptor類其實是在SqlSessionFactory中添加了攔截器,那么性能如何呢?下面是我這邊實際測試的時長對比

不添加sql重寫:

在這里插入圖片描述

添加sql重寫:

在這里插入圖片描述

根據測試時間可以看出,加了sql重寫會比未加的時候的時間長了11ms。
因為涉及到攔截以及反射,性能比不加的時候會稍微耗時一些,
如果是訪問量不高的情況下,用戶對于加沒加sql重寫的主觀感應時長其實差不多,因此這種sql重寫方案適合后臺訪問并發量不高的情況使用。
如果是并發量較高且比較注重性能的情況下,不建議加。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/906854.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/906854.shtml
英文地址,請注明出處:http://en.pswp.cn/news/906854.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

go多線程壓測監控

實現了 go多協程壓力測試實現了Monitor&#xff0c;異步統計qps、時延、cpu(client端)等指標&#xff0c;周期printStat。只需要把單條執行func傳給Monitor即可命令行傳參ctrlc之后正常退出(mock cpu 占用) 代碼見 https://gitee.com/bbjg001/golearning/tree/master/others/…

安卓無障礙腳本開發全教程

文章目錄 第一部分&#xff1a;無障礙服務基礎1.1 無障礙服務概述核心功能&#xff1a; 1.2 基本原理與架構1.3 開發環境配置所需工具&#xff1a;關鍵依賴&#xff1a; 第二部分&#xff1a;創建基礎無障礙服務2.1 服務聲明配置2.2 服務配置文件關鍵屬性說明&#xff1a; 2.3 …

閑時處理技術---CAD C#二次開發

在CAD C#二次開發中&#xff0c;使用閑時處理技術可以提高程序的響應性能和資源利用率。以下是一般的實現步驟&#xff1a; 1. 了解CAD的事件機制 CAD提供了一些事件&#xff0c;如 Idle 事件&#xff0c;當CAD應用程序處于空閑狀態時會觸發該事件。你可以訂閱這個事件來執行閑…

Git研究

以下命令在CentOS系統下執行 創建Git倉庫 git init git-example 監控.git目錄的變化情況&#xff1a; watch -n .5 tree .git 寫入文件內容&#xff0c;并把文件添加到Stage暫存區 echo 1 > t.txtgit add 1.txt 觀察結果如下&#xff1a;objects下多出了一個d00491fd…

野火魯班貓(arrch64架構debian)從零實現用MobileFaceNet算法進行實時人臉識別(四)安裝RKNN Toolkit Lite2

RKNN Toolkit Lite2 是瑞芯微專為RK系列芯片開發的NPU加速推理API。若不使用該工具&#xff0c;計算任務將僅依賴CPU處理&#xff0c;無法充分發揮芯片高達6TOPS的NPU算力優勢。 按照官方文檔先拉一下官方代碼庫&#xff0c;然后通過whl文件安裝&#xff0c;因為我是python3.1…

Vue3集成Element Plus完整指南:從安裝到主題定制下-實現后臺管理系統框架搭建

本文將詳細介紹如何使用 Vue 3 構建一個綜合管理系統&#xff0c;包括路由配置、頁面布局以及常用組件集成。 一、路由配置 首先&#xff0c;我們來看系統的路由配置&#xff0c;這是整個應用的基礎架構&#xff1a; import {createRouter, createWebHistory} from vue-rout…

【Oracle】創建公共數據連接

需求描述 兩個oracle數據庫&#xff0c;想從B數據庫創建視圖腳本訪問A數據庫相關表的數據&#xff0c;該怎么訪問呢&#xff1f; 解決方法 在Oracle數據庫中&#xff0c;創建公共數據庫鏈接&#xff08;Public Database Link&#xff09;可以允許數據庫中的任何用戶訪問遠程…

時序數據庫IoTDB的分片與負載均衡策略深入解析

一、引言 隨著數據庫服務的業務負載增加&#xff0c;擴展服務資源成為必然需求。擴展方式主要分為縱向擴展和橫向擴展。縱向擴展通過增加單臺機器的能力&#xff08;如內存、硬盤、處理器&#xff09;來實現&#xff0c;但受限于單臺機器的硬件能力。而橫向擴展則通過增加更多…

計算機網絡期末復習資料

我用夸克網盤分享了「計算機網絡」&#xff0c; 鏈接&#xff1a;https://pan.quark.cn/s/8aac2f0b840e 計算機網絡試題庫 1單項選擇題 1.1以下屬于物理層的設備是 ( A) A. 中繼器 B.以太網交換機 C. 橋 D. 網關 1.2在以太網中&#xff0c;是根據 (B) 地址來區分…

【IEEE 2025】低光增強KANT(使用KAN代替MLP)----論文詳解與代碼解析

【IEEE 2025】本文參考論文Enhancing Low-Light Images with Kolmogorov–Arnold Networks in Transformer Attention 雖然不是頂刊&#xff0c;但是有值得學習的地方 論文地址&#xff1a;arxiv 源碼地址&#xff1a;github 文章目錄 Part1 --- 論文精讀Part2 --- 代碼詳解形狀…

naivechain:簡易區塊鏈實現

naivechain&#xff1a;簡易區塊鏈實現 naivechain A naive and simple implementation of blockchains. 項目地址: https://gitcode.com/gh_mirrors/nai/naivechain 項目介紹 naivechain 是一個簡單且易于理解的區塊鏈實現項目。它使用 Go 語言編寫&#xff0c;以極簡…

Zabbix開源監控的全面詳解!

一、zabbix的基本概述 zabbix&#xff0c;這款企業級監控軟件&#xff0c;能全方位監控各類網絡參數&#xff0c;確保企業服務架構的安全穩定運行。它提供了靈活多樣的告警機制&#xff0c;幫助運維人員迅速發現并解決問題。此外&#xff0c;zabbix還具備分布式監控功能&#…

軟考軟件評測師——軟件工程之開發模型與方法

目錄 一、核心概念 二、主流模型詳解 &#xff08;一&#xff09;經典瀑布模型 &#xff08;二&#xff09;螺旋演進模型 &#xff08;三&#xff09;增量交付模型 &#xff08;四&#xff09;原型驗證模型 &#xff08;五&#xff09;敏捷開發實踐 三、模型選擇指南 四…

50天50個小項目 (Vue3 + Tailwindcss V4) ? | Blurry Loading (毛玻璃加載)

&#x1f4c5; 我們繼續 50 個小項目挑戰&#xff01;—— Blurry Loading 組件 倉庫地址&#xff1a;https://github.com/SunACong/50-vue-projects 項目預覽地址&#xff1a;https://50-vue-projects.vercel.app/ ? 組件目標 實現一個加載進度條&#xff0c;隨著加載進度的…

WPF性能優化之延遲加載(解決頁面卡頓問題)

文章目錄 前言一. 基礎知識回顧二. 問題分析三. 解決方案1. 新建一個名為DeferredContentHost的控件。2. 在DeferredContentHost控件中定義一個名為Content的object類型的依賴屬性&#xff0c;用于承載要加載的子控件。3. 在DeferredContentHost控件中定義一個名為Skeleton的ob…

VLM-MPC:自動駕駛中模型預測控制器增強視覺-語言模型

《VLM-MPC: Model Predictive Controller Augmented Vision Language Model for Autonomous Driving》2024年8月發表&#xff0c;來自威斯康星大學的論文。 受視覺語言模型&#xff08;VLM&#xff09;的緊急推理能力及其提高自動駕駛系統可理解性的潛力的啟發&#xff0c;本文…

推薦系統里真的存在“反饋循環”嗎?

推薦系統里真的存在“反饋循環”嗎&#xff1f; 許多人說&#xff0c;推薦算法不過是把用戶早已存在的興趣挖掘出來&#xff0c;你本來就愛聽流行歌、買潮牌玩具&#xff0c;系統只是在合適的時間把它們端到你面前&#xff0c;再怎么迭代&#xff0c;算法也改變不了人的天性&a…

代碼混淆技術的還原案例

案例一 eval 混淆 特征 &#xff1a; 反常的 eval 連接了一堆數據 練習網站 https://scrape.center/ spa9 這個案例 基本的還原方法 但是這個代碼還是非常的模糊不好看 優化一下 &#xff1a; 當然還有更快捷的方法 &#xff1a; 好用的 js混淆還原的 web &#xf…

鴻蒙Flutter實戰:22-混合開發詳解-2-Har包模式引入

以 Har 包的方式加載到 HarmonyOS 工程 創建工作 創建一個根目錄 mkdir ohos_flutter_module_demo這個目錄用于存放 flutter 項目和鴻蒙項目。 創建 Flutter 模塊 首先創建一個 Flutter 模塊&#xff0c;我們選擇與 ohos_app 項目同級目錄 flutter create --templatemodu…

Go核心特性與并發編程

Go核心特性與并發編程 1. 結構體與方法&#xff08;擴展&#xff09; 高級結構體特性 // 嵌套結構體與匿名字段 type Employee struct {Person // 匿名嵌入Department stringsalary float64 // 私有字段 }// 構造函數模式 func NewPerson(name string, age int) *Pe…