mybatis plus很好,但是我被它坑了!

来源:https://www.cnblogs.com/waynaqua/archive/2023/10/31/17799918.html
-Advertisement-
Play Games

作者今天在開發一個後臺發送消息的功能時,由於需要給多個用戶發送消息,於是使用了 mybatis plus 提供的 saveBatch() 方法,在測試環境測試通過上預發佈後,測試反應發送消息介面很慢得等 5、6 秒,於是我就登錄預發佈環境查看執行日誌,發現是 mybatis plus 提供的 sav ...


作者今天在開發一個後臺發送消息的功能時,由於需要給多個用戶發送消息,於是使用了 mybatis plus 提供的 saveBatch() 方法,在測試環境測試通過上預發佈後,測試反應發送消息介面很慢得等 5、6 秒,於是我就登錄預發佈環境查看執行日誌,發現是 mybatis plus 提供的 saveBatch() 方法執行很慢導致,於是也就有了本篇文章。

mybatis plus 是一個流行的 ORM 框架,它基於 mybatis,提供了很多便利的功能,比如代碼生成器、通用 CRUD、分頁插件、樂觀鎖插件等。它可以讓我們更方便地操作資料庫,減少重覆的代碼,提高開發效率。

註意:本文所使用的 mybatis plus 版本是 3.5.2 版本。

案發現場還原

/**
 * 先保存通知消息,在批量保存用戶通知記錄
 */
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveNotice(Notify notify, String receiveUserIds) {
    long begin = System.currentTimeMillis();
    notify.setCreateTime(new Date());
    notify.setCreateBy(ShiroUtil.getSessionUid());
    if (notify.getPublishTime() == null) {
        notify.setPublishTime(new Date());
    }
    boolean insert = save(notify);
    List<NotifyRecord> collect = new ArrayList<>();
    List<String> receiveUserList = fillNotifyRecordList(notify, receiveUserIds, collect);
    notifyRecordService.saveBatch(collect);
    long end = System.currentTimeMillis();
    System.out.println(end - begin);
    ...
    return insert;
}

/**
 * 根據用戶id,組裝用戶通知記錄集合,返回200條記錄
 */
public List<String> fillNotifyRecordList(Notify notify, String receiveUserIds, List<NotifyRecord> collect) {
    List<String> noticeRecordList = new ArrayList<>(200);
    ...
    // 組將兩百條用戶通知記錄
    return noticeRecordList;
}

如上代碼,我有一個 saveNotice() 方法用於保存通知消息以及用戶通知記錄。執行邏輯如下,

  1. 保存通知消息
  2. 根據用戶 id,組裝用戶通知記錄集合,返回 200 條用戶通知記錄
  3. 批量保存用戶通知記錄集合

前兩步驟耗時都很少,我們直接看第三步操作耗時,結合 sql 執行日誌,如下,

-- slow sql 5542 millis. INSERT INTO oa_notify_record  ( notifyId, receiveUserId, receiveUserName, isRead,  createTime )  VALUES  ( ?, ?, ?, ?,  ? )[225,"fcd90fe3990e505d07c90a238f75e9c1","niuwawa",false,"2023-10-30 23:54:04"]
5681

再結合 mybatis free log 插件列印完整 sql 如下圖,

 

可以看出,我們批量保存用戶通知記錄是一條一條保存得,已經可以猜測就是批量插入方法導致耗時較高。

這裡使用 mybatis log free 插件,它可以自動幫我們在控制台列印完整得 mybatis sql 語句。有需要可以在 idea 插件中心搜索 mybatis log free 下載安裝。

結合 saveBatch() 底層源碼也能夠看出,mybatis plus 對於批量操作是在 executeBatch() 方法內使用 for 迴圈執行插入操作得,源碼如下圖,

 

 

到這裡我們應該也能猜出了在測試環境執行較快得原因,因為在測試環境需要批量保存得用戶通知記錄比較少,只有幾條記錄,所以很快。但是上預發佈後,由於預發佈中需要批量保存得用戶通知記錄比較多達到了數百條,所以執行較慢,耗時達到了 5、6 秒之久。

由上述源碼可以看出,mybatis plus 的批量操作底層使用的還是 mybatis 提供的 batch 模式實現批量插入以及更新的。而 mybatis 提供的 batch 模式操作底層使用的還是 jdbc 驅動提供的批量操作模式,jdbc 批量操作示例代碼如下,

public static void main(String[] args) {
    Connection conn = null;
    PreparedStatement statement = null;
    try {
        // 資料庫連接
        String url = "jdbc:mysql://*************?autoReconnect=true&nullCatalogMeansCurrent=true&failOverReadOnly=false&useUnicode=true&characterEncoding=UTF-8";
        String user = "******";
        String password = "************";
        // 添加批處理參數
//            url = url + "&rewriteBatchedStatements=true";
        // 載入驅動類
        Class.forName("com.mysql.cj.jdbc.Driver");
        // 創建連接
        conn = DriverManager.getConnection(url, user, password);
        // 創建預編譯 sql 對象
        statement = conn.prepareStatement("UPDATE table_test_demo set code = ? where id = ?");
        long a = System.currentTimeMillis(); // 計時
        // 這裡添加 100 個批處理參數
        for (int i = 1; i <= 100; i++) {
            statement.setString(1, "測試1");
            statement.setInt(2, i);
            statement.addBatch(); // 批量添加
        }

        long b = System.currentTimeMillis(); // 計時
        System.out.println("添加參數耗時:" + (b-a)); // 計時

        int[] r = statement.executeBatch(); // 批量提交
        statement.clearBatch(); // 清空批量添加的 sql 命令列表緩存

        long c = System.currentTimeMillis(); // 計時
        System.out.println("執行sql耗時:" + (c-b)); // 計時
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 主動釋放資源
        try {
            if (statement != null) {
                statement.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}
  • statement.addBatch() 將 sql 語句打包到一個容器中
  • statement.executeBatch() 將容器中的 sql 語句提交
  • statement.clearBatch() 清空容器,為下一次打包做準備

推薦博主開源的 H5 商城項目waynboot-mall,這是一套全部開源的微商城項目,包含三個項目:運營後臺、H5 商城前臺和服務端介面。實現了商城所需的首頁展示、商品分類、商品詳情、商品 sku、分詞搜索、購物車、結算下單、支付寶/微信支付、收單評論以及完善的後臺管理等一系列功能。 技術上基於最新得 Springboot3.0、jdk17,整合了 MySql、Redis、RabbitMQ、ElasticSearch 等常用中間件。分模塊設計、簡潔易維護,歡迎大家點個 star、關註博主。

github 地址:https://github.com/wayn111/waynboot-mall

那麼問題出現在哪裡了?明明已經使用了批量操作,但耗時還是很慢,別急,跟著我往下看。

解決方法

到這裡,也就是本文得重點所在了,那怎麼解決這個問題嘞?如何既利用 mybatis plus 提供得便攜性,也能夠解決批量操作耗時較高得問題。

雖然我們使用了 mybatis plus -> mybatis -> jdbc 這一條批量操作鏈路,但是其實我們還需要在 jdbcurl 上添加一個 rewriteBatchedStatements=true 參數即可解決這個問題。

MySQL 的 JDBC 連接的 url 中要加 rewriteBatchedStatements 參數,並保證 5.1.13 以上版本的驅動,才能實現高性能的批量插入。

MySQL JDBC 驅動在預設情況下會無視 executeBatch()語句,把我們期望批量執行的一組 sql 語句拆散,一條一條地發給 MySQL 資料庫,批量插入實際上是單條插入,直接造成較低的性能。只有把 rewriteBatchedStatements 參數置為 true, 驅動才會幫你批量執行 SQL。另外這個選項對 INSERT/UPDATE/DELETE 都有效。

rewriteBatchedStatements=true 的意思是,當你在 Java 程式中使用批量插入/修改/刪除(batching)時,MySQL JDBC 驅動程式將嘗試重新編寫(rewrite)你的 SQL 語句,以便更有效地執行這些批量插入操作。

OK,在我們給 jdbcurl 上添加了參數後,看看效果,如下圖,

 

可以看到 jdbcurl 添加了 rewriteBatchedStatements=true 參數後,批量操作的執行耗時已經只有 200 毫秒,自此也就解決了 mybatis plus 提供的 saveBatch() 方法執行耗時較高得問題。

總結

mybatis plus 給開發人員帶來了很多便利,但是其中也有一些坑點,比如上文所提到得批量操作耗時問題,如果不註意的話,就有可能調入坑裡,各位開發同學可以檢查自己或者公司項目中 jdbcurl 是否缺失 rewriteBatchedStatements=true 參數,加以改正,避免重覆掉入這個坑裡。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 來源:同程藝龍 會員系統是一種基礎系統,跟公司所有業務線的下單主流程密切相關。如果會員系統出故障,會導致用戶無法下單,影響範圍是全公司所有業務線。所以,會員系統必須保證高性能、高可用,提供穩定、高效的基礎服務。 隨著同程和藝龍兩家公司的合併,越來越多的系統需要打通同程 APP、藝龍 APP、同程微信 ...
  • 每當一個網站崩潰,在座的各位都有一定的責任。 當一個爬蟲教程不火的時候還好,火起來了,就到了考驗網站伺服器的時候了,上一次茶杯狐就是這樣,還好人家頑強… 好了話不多說,直接開始。 首先就是必備的軟體和模塊 環境使用 Python 3.8 Pycharm 模塊使用 requests --> pip i ...
  • Java9及以後的版本引入了模塊化特性,實際實踐了一段時間之後發現“真香!”現在把“利用Java模塊化精簡JRE”的方法和經驗分享給大家。 ...
  • 需求 任務隊列中可以依次添加任務; 任務執行函數需要接受外部傳輸的參數; 主動調用Start開始執行任務; 代碼實現 class TaskQueue { private: std::mutex mtx; std::condition_variable cv; std::queue<std::func ...
  • 用python添加參數都是用的input函數,不能添加預設值也不能輸入help提示。 最近發現了2個更好用的庫分享給大家。 一、使用input庫。 這個使用很簡單,就不過多描述了。 def test(a,b): print(f"{a}+{b}=" + str(int(a)+int(b)) ) if ...
  • super相關的介紹文章看了無數遍,每次看得都雲里霧裡的,沒過多久就忘了,只模糊知道跟MRO有關,但是稍微一複雜就不知道怎麼回事了,本篇文章主要記錄我對super的理解 1.粗暴簡單的理解 super的作用就是執父類的方法,雖然這句話不完全對,但是也差不多是那麼個意思了。 比如以單繼承為例 clas ...
  • 需求:有一個vo類,該類繼承了一個實體類,獲取到vo對象後,需要將其中的null值轉為空字元串; 思路:傳入參數,用Object接收,利用反射獲取到該對象的所有欄位,並判斷置空; 由於一開始沒有考慮到父類的欄位獲取,導致時不時出現錯誤,因此這裡簡單記錄一下。 // 無需返回object,set後對象 ...
  • File --JAVA 構造方法 方法說明 public File (String pathname) 根據文件路徑創建對象 public File (String parent, String child) 根據父路徑名字字元串和子路徑名字元串創建文件對象 public File (String ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...