亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

如何使用MyBatis/MyBatis?Plus實現(xiàn)SQL日志打印與執(zhí)行監(jiān)控

 更新時間:2025年05月16日 09:42:18   作者:sjsjsbbsbsn  
MyBatis默認的日志輸出僅顯示帶占位符的SQL語句,無法直接看到實際參數(shù)值,且缺乏執(zhí)行時間統(tǒng)計,本文將介紹兩種實現(xiàn)方案,感興趣的朋友跟隨小編一起看看吧

使用MyBatis/MyBatis Plus實現(xiàn)SQL日志打印與執(zhí)行監(jiān)控

一、背景與價值

在開發(fā)過程中,SQL日志的完整輸出對于調(diào)試和性能優(yōu)化至關重要。MyBatis默認的日志輸出僅顯示帶占位符的SQL語句,無法直接看到實際參數(shù)值,且缺乏執(zhí)行時間統(tǒng)計。本文將介紹兩種實現(xiàn)方案:

  • 原生配置方案:通過日志框架直接輸出基礎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語句,使得結果返回完整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ù)庫類型的轉換
            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;
    }
}

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

四.總結

通過合理配置SQL日志輸出,開發(fā)者可以:

  • 快速定位SQL執(zhí)行問題
  • 直觀分析實際執(zhí)行的SQL語句
  • 有效識別性能瓶頸
  • 提升動態(tài)SQL調(diào)試效率

到此這篇關于使用MyBatisMyBatis Plus實現(xiàn)SQL日志打印與執(zhí)行監(jiān)控的文章就介紹到這了,更多相關MyBatisMyBatis Plus SQL日志打印內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論