如何使用MyBatis/MyBatis?Plus實現(xiàn)SQL日志打印與執(zhí)行監(jiān)控
使用MyBatis/MyBatis Plus實現(xiàn)SQL日志打印與執(zhí)行監(jiān)控
一、背景與價值
在開發(fā)過程中,SQL日志的完整輸出對于調(diào)試和性能優(yōu)化至關(guān)重要。MyBatis默認的日志輸出僅顯示帶占位符的SQL語句,無法直接看到實際參數(shù)值,且缺乏執(zhí)行時間統(tǒng)計。本文將介紹兩種實現(xiàn)方案:
- 原生配置方案:通過日志框架直接輸出基礎(chǔ)SQL日志
- 增強方案:使用MyBatis攔截器實現(xiàn)完整SQL打印和執(zhí)行監(jiān)控
二、原生配置方案(快速上手)
1. 日志框架配置(以Logback為例)
<!-- logback-spring.xml -->
<configuration>
<logger name="com.zaxxer.hikari" level="INFO"/>
<logger name="java.sql.Connection" level="INFO"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
</configuration>2. 輸出示例
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE name = ?
DEBUG [main] - ==> Parameters: John(String)
3. 局限性
- 參數(shù)值單獨顯示,無法直接拼接完整SQL
- 缺乏執(zhí)行耗時統(tǒng)計
- 動態(tài)SQL處理不夠直觀
三、增強方案:自定義攔截器實現(xiàn)
1. SQL美化與參數(shù)替換
public class MybatisPlusAllSqlLog implements InnerInterceptor {
public static final Logger log = LoggerFactory.getLogger("sys-sql");
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
logInfo(boundSql, ms, parameter);
}
@Override
public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
logInfo(boundSql, ms, parameter);
}
private static void logInfo(BoundSql boundSql, MappedStatement ms, Object parameter) {
try {
log.info("parameter = " + parameter);
// 獲取到節(jié)點的id,即sql語句的id
String sqlId = ms.getId();
log.info("sqlId = " + sqlId);
// 獲取節(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語句,使得結(jié)果返回完整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();
// sql語句中多個空格都用一個空格代替
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
// 獲取類型處理器注冊器,類型處理器的功能是進行java類型和數(shù)據(jù)庫類型的轉(zhuǎn)換
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
// 如果根據(jù)parameterObject.getClass()可以找到對應(yīng)的類型,則替換
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?",
Matcher.quoteReplacement(getParameterValue(parameterObject)));
} else {
// MetaObject主要是封裝了originalObject對象,提供了get和set的方法用于獲取和設(shè)置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,則添加單引號, 如果是日期,則轉(zhuǎn)換為時間格式器并加單引號; 對參數(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;
}
}2. 執(zhí)行耗時監(jiān)控
@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, CacheKey.class, BoundSql.class})})
public class SqlStatementInterceptor implements Interceptor {
public static final Logger log = LoggerFactory.getLogger("sys-sql");
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long timeConsuming = System.currentTimeMillis() - startTime;
log.info("執(zhí)行SQL:{}ms", timeConsuming);
if (timeConsuming > 999 && timeConsuming < 5000) {
log.info("執(zhí)行SQL大于1s:{}ms", timeConsuming);
} else if (timeConsuming >= 5000 && timeConsuming < 10000) {
log.info("執(zhí)行SQL大于5s:{}ms", timeConsuming);
} else if (timeConsuming >= 10000) {
log.info("執(zhí)行SQL大于10s:{}ms", timeConsuming);
}
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}自定義攔截器之后,請注意配置該攔截器
3.輸出示例
INFO [http-nio-8080-exec-1] - SQLID: com.example.mapper.UserMapper.selectById
INFO [http-nio-8080-exec-1] - 完整SQL: SELECT id,name,age FROM user WHERE id=1
INFO [http-nio-8080-exec-1] - 執(zhí)行耗時: 48ms
WARN [http-nio-8080-exec-1] - 慢SQL警告: 執(zhí)行耗時1204ms
四.總結(jié)
通過合理配置SQL日志輸出,開發(fā)者可以:
- 快速定位SQL執(zhí)行問題
- 直觀分析實際執(zhí)行的SQL語句
- 有效識別性能瓶頸
- 提升動態(tài)SQL調(diào)試效率
到此這篇關(guān)于使用MyBatisMyBatis Plus實現(xiàn)SQL日志打印與執(zhí)行監(jiān)控的文章就介紹到這了,更多相關(guān)MyBatisMyBatis Plus SQL日志打印內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- mybatis-plus開啟sql日志打印的三種方法
- mybatis-plus如何修改日志只打印SQL語句不打印查詢結(jié)果
- mybatis plus 開啟sql日志打印的方法小結(jié)
- 自主配置數(shù)據(jù)源,mybatis/plus不打印sql日志問題
- Mybatis-Plus打印sql日志兩種方式
- 服務(wù)性能優(yōu)化之mybatis-plus開啟與關(guān)閉SQL日志打印方法
- MyBatis-Plus如何關(guān)閉SQL日志打印詳解
- MyBatis Plus關(guān)閉SQL日志打印的方法
- MyBatis-Plus使用sl4j日志打印SQL的代碼詳解
- Mybatis-Plus通過配置在控制臺打印執(zhí)行日志的實現(xiàn)
- 使用MybatisPlus實現(xiàn)sql日志打印優(yōu)化
相關(guān)文章
StringRedisTemplate操作hash實現(xiàn)過程
文章總結(jié)了使用StringRedisTemplate操作Redis哈希(hash)的基本示例,分享了個人經(jīng)驗,旨在為讀者提供參考,并鼓勵大家支持腳本之家2026-03-03
JAVA實現(xiàn)數(shù)字大寫金額轉(zhuǎn)換的方法
這篇文章主要介紹了JAVA實現(xiàn)數(shù)字大寫金額轉(zhuǎn)換的方法,涉及java針對字符串與數(shù)組的遍歷與轉(zhuǎn)換相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-07-07
Java使用Spire.PDF for Java實現(xiàn)旋轉(zhuǎn)PDF頁面
在日常的文檔處理工作中,我們經(jīng)常會遇到 PDF 頁面方向不正確的問題,本文將深入探討如何利用 Java 實現(xiàn) PDF 頁面的旋轉(zhuǎn)操作,感興趣的小伙伴可以了解下2025-09-09
visual studio 2019安裝配置可編寫c/c++語言的IDE環(huán)境
這篇文章主要介紹了visual studio 2019安裝配置可編寫c/c++語言的IDE環(huán)境,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
SpringBoot中的application.properties無法加載問題定位技巧
這篇文章主要介紹了SpringBoot中的application.properties無法加載問題定位技巧,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05

