最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MyBatis-Plus批量操作SQL日志不打印問(wèn)題的解決方案

 更新時(shí)間:2026年03月05日 08:57:09   作者:程序員食堂  
在使用 MyBatis-Plus 的?saveBatch()?和?updateBatchById()?方法進(jìn)行批量數(shù)據(jù)操作時(shí),發(fā)現(xiàn)自定義的 Druid SQL 日志攔截器無(wú)法打印這些批量操作的 SQL 語(yǔ)句,導(dǎo)致調(diào)試和問(wèn)題排查困難,本文給大家該問(wèn)題的詳細(xì)解決方案,需要的朋友可以參考下

問(wèn)題描述

在使用 MyBatis-Plus 的 saveBatch() 和 updateBatchById() 方法進(jìn)行批量數(shù)據(jù)操作時(shí),發(fā)現(xiàn)自定義的 Druid SQL 日志攔截器(SqlLogInterceptor)無(wú)法打印這些批量操作的 SQL 語(yǔ)句,導(dǎo)致調(diào)試和問(wèn)題排查困難。

問(wèn)題分析

1. 批量操作的特殊性

MyBatis-Plus 的批量操作方法(如 saveBatch、updateBatchById)使用了 JDBC 的批處理(Batch)模式來(lái)提高性能。批處理模式與普通的單條 SQL 執(zhí)行方式不同:

  • 普通執(zhí)行:每條 SQL 單獨(dú)執(zhí)行,會(huì)觸發(fā) statementExecuteAfter、statementExecuteUpdateAfter 等方法
  • 批處理執(zhí)行:多條 SQL 一起提交執(zhí)行,只會(huì)觸發(fā) statementExecuteBatchAfter 方法

2. 原有攔截器的問(wèn)題

原有的 SqlLogInterceptor 繼承自 Druid 的 FilterEventAdapter,實(shí)現(xiàn)了以下方法:

@Override
protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
    statement.setLastExecuteTimeNano();
    printSqlLog(statement, sql);
}

@Override
protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) {
    statement.setLastExecuteTimeNano();
    printSqlLog(statement, sql);
}

@Override
protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {
    statement.setLastExecuteTimeNano();
    // 這里沒(méi)有打印日志!
}

可以看到,statementExecuteBatchAfter 方法中只設(shè)置了執(zhí)行時(shí)間,但沒(méi)有打印 SQL 日志,這就是批量操作 SQL 不打印的根本原因。

解決方案

方案一:修改 Druid 攔截器(推薦)

修改 SqlLogInterceptor.java 的 statementExecuteBatchAfter 方法,添加 SQL 日志打印邏輯:

@Override
protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {
    statement.setLastExecuteTimeNano();
    // 批量執(zhí)行后也打印SQL日志
    String sql = statement.getBatchSql();
    if (StringUtil.isNotBlank(sql)) {
        printSqlLog(statement, sql);
    } else {
        // 如果批量SQL為空,嘗試獲取最后執(zhí)行的SQL
        sql = statement.getLastExecuteSql();
        if (StringUtil.isNotBlank(sql)) {
            printSqlLog(statement, sql);
        }
    }
}

優(yōu)點(diǎn)

  • 統(tǒng)一的日志格式
  • 包含執(zhí)行時(shí)間統(tǒng)計(jì)
  • 可以格式化 SQL 和參數(shù)

缺點(diǎn)

  • 批量操作可能只顯示一條 SQL 模板,看不到每條具體的參數(shù)

方案二:?jiǎn)⒂?MyBatis 原生日志

在 logback.xml 配置文件中添加 MyBatis Mapper 的 DEBUG 級(jí)別日志:

<!-- MyBatis SQL 日志 -->
<logger name="com.hzys.mapper" level="DEBUG"/>

優(yōu)點(diǎn)

  • 可以看到每條 SQL 的詳細(xì)參數(shù)
  • MyBatis 原生支持,穩(wěn)定可靠

缺點(diǎn)

  • 日志格式與自定義攔截器不一致
  • 日志量較大

方案三:雙管齊下(最佳實(shí)踐)

同時(shí)使用方案一和方案二,既能保證批量操作的 SQL 被記錄,又能在需要時(shí)查看詳細(xì)的參數(shù)信息。

完整代碼示例

1. 修改后的 SqlLogInterceptor

@Slf4j
public class SqlLogInterceptor extends FilterEventAdapter {
    private static final SQLUtils.FormatOption FORMAT_OPTION = new SQLUtils.FormatOption(false, false);

    @Override
    protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {
        statement.setLastExecuteTimeNano();
        // 批量執(zhí)行后也打印SQL日志
        String sql = statement.getBatchSql();
        if (StringUtil.isNotBlank(sql)) {
            printSqlLog(statement, sql);
        } else {
            // 如果批量SQL為空,嘗試獲取最后執(zhí)行的SQL
            sql = statement.getLastExecuteSql();
            if (StringUtil.isNotBlank(sql)) {
                printSqlLog(statement, sql);
            }
        }
    }

    private void printSqlLog(StatementProxy statement, String sql) {
        if (!log.isInfoEnabled() || StringUtil.isEmpty(sql)) {
            return;
        }

        try {
            // 獲取參數(shù)
            int parametersSize = statement.getParametersSize();
            List<Object> parameters = new ArrayList<>(parametersSize);
            for (int i = 0; i < parametersSize; ++i) {
                parameters.add(getJdbcParameter(statement.getParameter(i)));
            }

            // 格式化SQL
            String dbType = statement.getConnectionProxy().getDirectDataSource().getDbType();
            String formattedSql = SQLUtils.format(sql, DbType.of(dbType), parameters, FORMAT_OPTION);

            // 打印日志
            printSql(formattedSql, statement);
        } catch (Exception e) {
            log.error("SQL 格式化失敗", e);
            log.info("\n\n==============  Sql Start  ==============\n" +
                    "Execute SQL : {}\n" +
                    "Execute Time: {}\n" +
                    "==============  Sql  End   ==============\n",
                    sql, StringUtil.format(statement.getLastExecuteTimeNano()));
        }
    }

    private static void printSql(String sql, StatementProxy statement) {
        String sqlLogger = "\n\n==============  Sql Start  ==============" +
                "\nExecute SQL : {}" +
                "\nExecute Time: {}" +
                "\n==============  Sql  End   ==============\n";
        log.info(sqlLogger, sql.trim(), StringUtil.format(statement.getLastExecuteTimeNano()));
    }
}

2. Logback 配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
    <!-- 其他配置... -->

    <!-- 日志輸出級(jí)別 -->
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="INFO"/>
        <appender-ref ref="WARN"/>
        <appender-ref ref="ERROR"/>
    </root>

    <!-- MyBatis SQL 日志 -->
    <logger name="com.hzys.mapper" level="DEBUG"/>
    
    <logger name="net.sf.ehcache" level="INFO"/>
    <logger name="druid.sql" level="INFO"/>
</configuration>

3. Druid 配置

@Slf4j
@Configuration
public class DruidConfig {

    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.druid")
    public DataSource dataSource() {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();

        // 添加自定義的SQL日志攔截器
        SqlLogInterceptor sqlLogInterceptor = new SqlLogInterceptor();
        dataSource.getProxyFilters().add(sqlLogInterceptor);

        return dataSource;
    }
}

驗(yàn)證效果

修改后,執(zhí)行批量操作時(shí)會(huì)看到類(lèi)似以下的日志輸出:

==============  Sql Start  ==============
Execute SQL : INSERT INTO employee_hourly_rate (id, project_member, member_level, ...) VALUES (?, ?, ?, ...)
Execute Time: 15ms
==============  Sql  End   ==============

==>  Preparing: INSERT INTO employee_hourly_rate (id, project_member, member_level, ...) VALUES (?, ?, ?, ...)
==> Parameters: 1(Long), 張三(String), 高級(jí)工程師(String), ...
==> Parameters: 2(Long), 李四(String), 中級(jí)工程師(String), ...
<==    Updates: 2

注意事項(xiàng)

  1. 性能考慮:DEBUG 級(jí)別的 MyBatis 日志會(huì)輸出大量信息,生產(chǎn)環(huán)境建議關(guān)閉或設(shè)置為 INFO 級(jí)別
  2. 日志過(guò)濾:可以在 SqlLogInterceptor 中添加過(guò)濾邏輯,避免打印某些不需要的 SQL(如健康檢查)
  3. 批量大小:MyBatis-Plus 默認(rèn)批量大小為 1000,可以通過(guò)配置調(diào)整
  4. 事務(wù)管理:批量操作需要在事務(wù)中執(zhí)行,確保添加 @Transactional 注解

總結(jié)

MyBatis-Plus 批量操作 SQL 不打印的問(wèn)題主要是因?yàn)榕幚砟J绞褂昧瞬煌膱?zhí)行路徑,原有的攔截器沒(méi)有處理 statementExecuteBatchAfter 方法。通過(guò)修改攔截器并配合 MyBatis 原生日志,可以完美解決這個(gè)問(wèn)題,既能保證日志的完整性,又能在需要時(shí)查看詳細(xì)的執(zhí)行信息。

以上就是MyBatis-Plus批量操作SQL日志不打印問(wèn)題的解決方案的詳細(xì)內(nèi)容,更多關(guān)于MyBatis-Plus操作SQL日志不打印的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java職責(zé)鏈模式的深入了解

    Java職責(zé)鏈模式的深入了解

    這篇文章主要為大家介紹了Java職責(zé)鏈模式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-01-01
  • Java之Spring簡(jiǎn)單的讀取和存儲(chǔ)對(duì)象

    Java之Spring簡(jiǎn)單的讀取和存儲(chǔ)對(duì)象

    這篇文章主要介紹了Spring的讀取和存儲(chǔ)對(duì)象,獲取 bean 對(duì)象也叫做對(duì)象裝配,是把對(duì)象取出來(lái)放到某個(gè)類(lèi)中,有時(shí)候也叫對(duì)象注?,想進(jìn)一步了解的同學(xué)可以參考本文
    2023-04-04
  • java?zxing合成復(fù)雜二維碼圖片示例詳解

    java?zxing合成復(fù)雜二維碼圖片示例詳解

    這篇文章主要為大家介紹了java?zxing合成復(fù)雜二維碼圖片示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • Spring實(shí)戰(zhàn)之使用靜態(tài)工廠方法創(chuàng)建Bean操作示例

    Spring實(shí)戰(zhàn)之使用靜態(tài)工廠方法創(chuàng)建Bean操作示例

    這篇文章主要介紹了Spring實(shí)戰(zhàn)之使用靜態(tài)工廠方法創(chuàng)建Bean操作,結(jié)合實(shí)例形式分析了靜態(tài)工廠方法創(chuàng)建Bean的相關(guān)實(shí)現(xiàn)步驟與操作注意事項(xiàng),需要的朋友可以參考下
    2019-11-11
  • SpringBoot自定義Starter的教程指南

    SpringBoot自定義Starter的教程指南

    SpringBoot的Starter自動(dòng)配置機(jī)制極大地簡(jiǎn)化了依賴(lài)管理和應(yīng)用配置,使得開(kāi)發(fā)者可以以最少的配置快速啟動(dòng)和運(yùn)行Spring應(yīng)用,有時(shí),標(biāo)準(zhǔn)的Starter可能無(wú)法滿(mǎn)足特定需求,需要?jiǎng)?chuàng)建自定義Starter,所以本文給大家介紹了SpringBoot自定義Starter的教程指南
    2024-11-11
  • Java實(shí)現(xiàn)加鹽算法的兩種方法

    Java實(shí)現(xiàn)加鹽算法的兩種方法

    數(shù)據(jù)安全是一個(gè)重要的問(wèn)題,本文主要介紹了Java實(shí)現(xiàn)加鹽算法的兩種方法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-02-02
  • win11?idea?shift+F6快捷鍵失效問(wèn)題解決方案

    win11?idea?shift+F6快捷鍵失效問(wèn)題解決方案

    這篇文章主要介紹了win11?idea?shift+F6快捷鍵失效問(wèn)題,本文給大家分享最新解決方案,需要的朋友可以參考下
    2023-08-08
  • 創(chuàng)建Java項(xiàng)目時(shí)常見(jiàn)的三種構(gòu)建系統(tǒng)詳解

    創(chuàng)建Java項(xiàng)目時(shí)常見(jiàn)的三種構(gòu)建系統(tǒng)詳解

    這篇文章主要介紹了創(chuàng)建Java項(xiàng)目時(shí)常見(jiàn)的三種構(gòu)建系統(tǒng),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-05-05
  • Spring websocket并發(fā)發(fā)送消息異常的解決

    Spring websocket并發(fā)發(fā)送消息異常的解決

    本文主要介紹了 Spring websocket并發(fā)發(fā)送消息異常的解決,當(dāng)多個(gè)線程同時(shí)嘗試通過(guò) WebSocket 會(huì)話(huà)發(fā)送消息時(shí),會(huì)拋出異常,下面就來(lái)解決一下,感興趣的可以了解一下
    2023-09-09
  • idea左下角的Git(Version Control)中顯示Local Changes窗口方式

    idea左下角的Git(Version Control)中顯示Local Changes窗口方式

    在IDEA中,通過(guò)使用快捷鍵Alt+9(Windows)或Cmd+9(Mac)可以快速打開(kāi)LocalChanges窗口,查看當(dāng)前Git倉(cāng)庫(kù)的本地變更,若此方法不可用,可嘗試進(jìn)入settings,點(diǎn)擊VersionControl,選擇Commit,并取消Use interface的勾選
    2024-10-10

最新評(píng)論

盖州市| 平潭县| 周口市| 蒙山县| 黄骅市| 四会市| 阿克苏市| 西平县| 新蔡县| 奉新县| 合阳县| 延庆县| 左云县| 扬中市| 东平县| 广昌县| 台南县| 宣武区| 竹北市| 察哈| 衡水市| 江油市| 四川省| 潼南县| 南江县| 北京市| 邹城市| 长沙县| 龙泉市| 阳泉市| 冷水江市| 丹江口市| 台山市| 堆龙德庆县| 岑溪市| 岗巴县| 平果县| 新竹县| 景东| 北流市| 旬邑县|