如何使用MyBatis/MyBatis?Plus實(shí)現(xiàn)SQL日志打印與執(zhí)行監(jiān)控
使用MyBatis/MyBatis Plus實(shí)現(xiàn)SQL日志打印與執(zhí)行監(jiān)控
一、背景與價(jià)值
在開發(fā)過程中,SQL日志的完整輸出對(duì)于調(diào)試和性能優(yōu)化至關(guān)重要。MyBatis默認(rèn)的日志輸出僅顯示帶占位符的SQL語句,無法直接看到實(shí)際參數(shù)值,且缺乏執(zhí)行時(shí)間統(tǒng)計(jì)。本文將介紹兩種實(shí)現(xiàn)方案:
- 原生配置方案:通過日志框架直接輸出基礎(chǔ)SQL日志
- 增強(qiáng)方案:使用MyBatis攔截器實(shí)現(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ù)值單獨(dú)顯示,無法直接拼接完整SQL
- 缺乏執(zhí)行耗時(shí)統(tǒng)計(jì)
- 動(dòng)態(tài)SQL處理不夠直觀
三、增強(qiáng)方案:自定義攔截器實(shí)現(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é)點(diǎn)的id,即sql語句的id
String sqlId = ms.getId();
log.info("sqlId = " + sqlId);
// 獲取節(jié)點(diǎn)的配置
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é)點(diǎn)id + sql語句
public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId) {
return sqlId + ":" + showSql(configuration, boundSql);
}
// 進(jìn)行?的替換
public static String showSql(Configuration configuration, BoundSql boundSql) {
// 獲取參數(shù)
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// sql語句中多個(gè)空格都用一個(gè)空格代替
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
// 獲取類型處理器注冊(cè)器,類型處理器的功能是進(jìn)行java類型和數(shù)據(jù)庫類型的轉(zhuǎn)換
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
// 如果根據(jù)parameterObject.getClass()可以找到對(duì)應(yīng)的類型,則替換
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?",
Matcher.quoteReplacement(getParameterValue(parameterObject)));
} else {
// MetaObject主要是封裝了originalObject對(duì)象,提供了get和set的方法用于獲取和設(shè)置originalObject的屬性值,主要支持對(duì)JavaBean、Collection、Map三種類型對(duì)象的操作
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)) {
// 該分支是動(dòng)態(tài)sql
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?",
Matcher.quoteReplacement(getParameterValue(obj)));
} else {
// 打印出缺失,提醒該參數(shù)缺失并防止錯(cuò)位
sql = sql.replaceFirst("\\?", "缺失");
}
}
}
}
return sql;
}
// 如果參數(shù)是String,則添加單引號(hào), 如果是日期,則轉(zhuǎn)換為時(shí)間格式器并加單引號(hào); 對(duì)參數(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í)行耗時(shí)監(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) {
}
}自定義攔截器之后,請(qǐng)注意配置該攔截器
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í)行耗時(shí): 48ms
WARN [http-nio-8080-exec-1] - 慢SQL警告: 執(zhí)行耗時(shí)1204ms
四.總結(jié)
通過合理配置SQL日志輸出,開發(fā)者可以:
- 快速定位SQL執(zhí)行問題
- 直觀分析實(shí)際執(zhí)行的SQL語句
- 有效識(shí)別性能瓶頸
- 提升動(dòng)態(tài)SQL調(diào)試效率
到此這篇關(guān)于使用MyBatisMyBatis Plus實(shí)現(xiàn)SQL日志打印與執(zhí)行監(jiān)控的文章就介紹到這了,更多相關(guān)MyBatisMyBatis Plus SQL日志打印內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(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通過配置在控制臺(tái)打印執(zhí)行日志的實(shí)現(xiàn)
- 使用MybatisPlus實(shí)現(xiàn)sql日志打印優(yōu)化
相關(guān)文章
你知道怎么從Python角度學(xué)習(xí)Java基礎(chǔ)
這篇文章主要為大家詳細(xì)介紹了Python角度學(xué)習(xí)Java基礎(chǔ)的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02
Java多線程之循環(huán)柵欄技術(shù)CyclicBarrier使用探索
這篇文章主要介紹了Java多線程之循環(huán)柵欄技術(shù)CyclicBarrier,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>2024-01-01
SpringBoot預(yù)加載與懶加載實(shí)現(xiàn)方法超詳細(xì)講解
Spring一直被詬病啟動(dòng)時(shí)間慢,可Spring/SpringBoot是輕量級(jí)的框架。因?yàn)楫?dāng)Spring項(xiàng)目越來越大的時(shí)候,在啟動(dòng)時(shí)加載和初始化Bean就會(huì)變得越來越慢,很多時(shí)候我們?cè)趩?dòng)時(shí)并不需要加載全部的Bean,在調(diào)用時(shí)再加載就行,那這就需要預(yù)加載與懶加載的功能了2022-11-11
解決@ConfigurationProperties注解的使用及亂碼問題
這篇文章主要介紹了解決@ConfigurationProperties注解的使用及亂碼問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
JavaWeb評(píng)論功能實(shí)現(xiàn)步驟以及代碼實(shí)例
項(xiàng)目初始版本上線,有時(shí)間寫點(diǎn)東西記錄一下項(xiàng)目中的心得體會(huì),通過這個(gè)項(xiàng)目學(xué)習(xí)了很多,要寫下來的有很多,先從評(píng)論功能開始吧,下面這篇文章主要給大家介紹了關(guān)于JavaWeb評(píng)論功能實(shí)現(xiàn)步驟以及代碼的相關(guān)資料,需要的朋友可以參考下2023-01-01
java 基礎(chǔ)之JavaBean屬性命名規(guī)范問題
這篇文章主要介紹了java 基礎(chǔ)之JavaBean屬性命名規(guī)范問題的相關(guān)資料,需要的朋友可以參考下2017-05-05
Javaweb開發(fā)環(huán)境Myeclipse6.5 JDK1.6 Tomcat6.0 SVN1.8配置教程
這篇文章主要介紹了Javaweb開發(fā)環(huán)境Myeclipse6.5 JDK1.6 Tomcat6.0 SVN1.8配置教程,感興趣的小伙伴們可以參考一下2016-06-06
java?poi之XWPFDocument如何讀取word內(nèi)容并創(chuàng)建新的word
這篇文章主要介紹了java?poi之XWPFDocument如何讀取word內(nèi)容并創(chuàng)建新的word問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04

