首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java MyBatis 攔截器實作 SQL 執行時間監控與慢查詢日誌

     編輯:Java 相關知識

Java MyBatis 攔截器實作 SQL 執行時間監控與慢查詢日誌

MyBatis 攔截器 (Interceptor) 機制允許在 SQL 執行前後攔截處理,常用於效能監控、分頁插件、資料權限過濾等。本文實作一個生產級的 SqlPerformanceInterceptor,自動記錄超過閾值的 SQL 執行時間、綁定參數與呼叫堆疊,助力慢查詢優化。

Maven 依賴

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.16</version>
</dependency>

攔截器完整實作

@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})
})
public class SqlPerformanceInterceptor implements Interceptor {

    private static final long SLOW_THRESHOLD_MS = 100; // 慢查詢閾值
    private static final Logger log = LoggerFactory.getLogger(SqlPerformanceInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        Object param = invocation.getArgs().length > 1 ? invocation.getArgs()[1] : null;
        
        long start = System.currentTimeMillis();
        try {
            return invocation.proceed(); // 執行原 SQL
        } finally {
            long elapsed = System.currentTimeMillis() - start;
            if (elapsed > SLOW_THRESHOLD_MS) {
                String sql = buildSql(ms, param);
                log.warn("[SLOW SQL] {} ms | ID: {} | SQL: {}", elapsed, ms.getId(), sql);
                // 可選:記錄呼叫堆疊定位業務代碼
                // log.debug("Stack: {}", Arrays.toString(Thread.currentThread().getStackTrace()));
            }
        }
    }

    private String buildSql(MappedStatement ms, Object param) {
        BoundSql boundSql = ms.getBoundSql(param);
        Configuration cfg = ms.getConfiguration();
        String sql = boundSql.getSql().replaceAll("\\s+", " "); // 壓縮空白
        
        // 參數替換 (簡易版,生產建議用 ParameterHandler)
        List mappings = boundSql.getParameterMappings();
        if (!mappings.isEmpty() && param != null) {
            TypeHandlerRegistry registry = cfg.getTypeHandlerRegistry();
            if (registry.hasTypeHandler(param.getClass())) {
                sql = sql.replaceFirst("\\?", formatParam(param));
            } else {
                MetaObject meta = cfg.newMetaObject(param);
                for (ParameterMapping pm : mappings) {
                    String prop = pm.getProperty();
                    Object val = meta.hasGetter(prop) ? meta.getValue(prop) 
                            : boundSql.hasAdditionalParameter(prop) ? boundSql.getAdditionalParameter(prop) : null;
                    sql = sql.replaceFirst("\\?", formatParam(val));
                }
            }
        }
        return sql;
    }

    private String formatParam(Object obj) {
        if (obj == null) return "NULL";
        if (obj instanceof String) return "'" + obj + "'";
        if (obj instanceof Date) return "'" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(obj) + "'";
        return obj.toString();
    }

    @Override public Object plugin(Object target) { return Plugin.wrap(target, this); }
    @Override public void setProperties(Properties props) { /* 可讀取 threshold 等配置 */ }
}

MyBatis 配置啟用

XML 配置 (mybatis-config.xml)

<plugins>
    <plugin interceptor="com.example.interceptor.SqlPerformanceInterceptor">
        <property name="slowThresholdMs" value="200"/>
    </plugin>
</plugins>

Spring Boot 整合

@Configuration
public class MyBatisConfig {
    @Bean
    public SqlPerformanceInterceptor sqlPerformanceInterceptor() {
        return new SqlPerformanceInterceptor();
    }
    
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return cfg -> cfg.addInterceptor(sqlPerformanceInterceptor());
    }
}

進階功能擴充建議

  • 參數格式化增強:集成 ParameterHandler 正確處理集合、陣列、TypeHandler 自訂類型。
  • 資料脫敏:日誌輸出前過濾敏感欄位 (身分證、手機、密碼)。
  • 異步寫入:使用 Logback AsyncAppender 或自建阻塞佇列 + 消費者執行緒,避免日誌 I/O 影響主流程效能。
  • 監控整合:配合 Micrometer + Prometheus 記錄 Histogram 指標,Grafana 看板告警。
  • SQL 格式化:引入 jsqlparser 美化輸出 SQL,提升可讀性。

效能影響評估

  • 攔截器本身開銷極低 (納秒級),主要耗時在 buildSql() 字串處理與日誌寫入。
  • 建議**僅記錄慢查詢**,避免全量 SQL 日誌造成磁碟 IO 壓力。
  • 高併發系統請務必啟用非同步日誌。

常見坑點

  1. 多參數映射:replaceFirst("?") 依序替換,若 SQL 含字面常數 ? 會誤替換,建議用正則 \\? 還要考慮預處理語句佔位符上下文。
  2. 分頁插件衝突:PageHelper 等分頁插件也攔截 Executor,順序由配置順序決定,測試驗證無副作用。
  3. 事務邊界:攔截器在事務內外均會觸發,若需事務級統計需自行關聯 TransactionSynchronizationManager。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved