使用MybatisPlus實現(xiàn)sql日志打印優(yōu)化
背景:
在排查無憂行后臺服務日志時,一個請求可能會包含多個執(zhí)行的sql,經常會遇到SQL語句與對應參數(shù)不連續(xù)顯示,或者參數(shù)較多需要逐個匹配的情況。這種情況下,如果需要還原完整SQL語句就會比較耗時。因此,我希望能優(yōu)化這一流程。
正文:
在平時排查sql執(zhí)行情況時,對于sql查詢參數(shù)比較多或者多個sql在同一個請求下執(zhí)行的時候會比較麻煩,需要查看每個參數(shù)的值一一對應檢查是否有異常,有時候還需要將查詢sql的問號還原去庫里面查詢結果,就類似下面的日志:

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

具體的思路以及實現(xiàn)方案,以無憂行項目為例
實現(xiàn)思路,利用mybatisplus提供的接口InnerInterceptor,重寫sql日志,并將重寫之后的類注入到sqlSessionFactory中,在執(zhí)行sql之前捕獲到并打印。
解釋一下InnerInterceptor類:InnerInterceptor 是 MyBatis-Plus 框架中的一個核心攔截器接口,它是 MyBatis 攔截器機制的擴展,用于在 SQL 執(zhí)行過程中進行攔截和增強。
InnerInterceptor工作原理:InnerInterceptor 通過 MyBatis 的插件機制工作,在以下時機進行攔截:
- beforeQuery:查詢操作執(zhí)行前 - beforeUpdate:更新操作執(zhí)行前 - beforePrepare:SQL 準備階段
針對查詢sql執(zhí)行的sql重整,使用的是beforeQuery,針對更新sql執(zhí)行的重整,使用的是beforeUpdate,以下是具體實現(xiàn):
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 {
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
log.info("beforeQuery");
logInfo(boundSql, ms, parameter);
}
@Override
public 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 {
// 獲取到節(jié)點的id,即sql語句的id
String sqlId = ms.getId();
// 獲取節(jié)點的配置
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語句節(jié)點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) {
// 獲取參數(shù)
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類型和數(shù)據(jù)庫類型的轉換
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
// 如果根據(jù)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)) {
// 該分支是動態(tài)sql
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?",
Matcher.quoteReplacement(getParameterValue(obj)));
} else {
// 打印出缺失,提醒該參數(shù)缺失并防止錯位
sql = sql.replaceFirst("\\?", "缺失");
}
}
}
}
return sql;
}
// 如果參數(shù)是String,則添加單引號, 如果是日期,則轉換為時間格式器并加單引號; 對參數(shù)是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 {
@Autowired
private List<SqlSessionFactory> sqlSessionFactoryList;
@Autowired
private MybatisPlusLogRewrite myInnerInterceptor;
/**
* 添加Mybatis攔截器
*
* @author zhouxy
* @date 2025/5/15 16:29
*/
@PostConstruct
public 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打印日志,執(zhí)行查詢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重寫:

根據(jù)測試時間可以看出,加了sql重寫會比未加的時候的時間長了11ms。
因為涉及到攔截以及反射,性能比不加的時候會稍微耗時一些,
如果是訪問量不高的情況下,用戶對于加沒加sql重寫的主觀感應時長其實差不多,因此這種sql重寫方案適合后臺訪問并發(fā)量不高的情況使用。
如果是并發(fā)量較高且比較注重性能的情況下,不建議加。
到此這篇關于使用MybatisPlus實現(xiàn)sql日志打印優(yōu)化的文章就介紹到這了,更多相關MybatisPlus sql日志打印內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- mybatis-plus開啟sql日志打印的三種方法
- mybatis-plus如何修改日志只打印SQL語句不打印查詢結果
- mybatis plus 開啟sql日志打印的方法小結
- 自主配置數(shù)據(jù)源,mybatis/plus不打印sql日志問題
- Mybatis-Plus打印sql日志兩種方式
- 服務性能優(yōu)化之mybatis-plus開啟與關閉SQL日志打印方法
- MyBatis-Plus如何關閉SQL日志打印詳解
- MyBatis Plus關閉SQL日志打印的方法
- MyBatis-Plus使用sl4j日志打印SQL的代碼詳解
- Mybatis-Plus通過配置在控制臺打印執(zhí)行日志的實現(xiàn)
- 如何使用MyBatis/MyBatis?Plus實現(xiàn)SQL日志打印與執(zhí)行監(jiān)控
相關文章
在Java中使用Redis實現(xiàn)緩存優(yōu)化的操作步驟
在現(xiàn)代高并發(fā)的應用中,數(shù)據(jù)庫訪問的性能往往成為瓶頸,為了提高性能,我們通常會使用緩存機制,Redis 是一種開源的內存數(shù)據(jù)存儲系統(tǒng),廣泛應用于緩存系統(tǒng)的構建中,本文將深入探討如何在Java中使用Redis實現(xiàn)緩存優(yōu)化,需要的朋友可以參考下2025-07-07
java根據(jù)不同的參數(shù)調用不同的實現(xiàn)類操作
這篇文章主要介紹了java根據(jù)不同的參數(shù)調用不同的實現(xiàn)類操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
Spring Cloud服務入口Gateway的介紹和使用問題小結
Spring Cloud Gateway是Spring Cloud的?個全新的API?關項?, 基于Spring + SpringBoot等技術開發(fā), ?的是為了替換掉Zuul,這篇文章主要介紹了Spring Cloud服務入口Gateway的介紹和使用問題小結,需要的朋友可以參考下2025-03-03
Java之Error與Exception的區(qū)別案例詳解
這篇文章主要介紹了Java之Error與Exception的區(qū)別案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下2021-09-09
Windows下將JAVA?jar注冊成windows服務的方法
這篇文章主要介紹了Windows下將JAVA?jar注冊成windows服務的方法,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07

