【SpringBoot DB系列】Jooq批次寫入採坑記錄

小灰灰Blog發表於2020-12-13

【SpringBoot DB系列】Jooq批次寫入採坑記錄

前面介紹了jooq的三種批次插入方式,結果最近發現這裡面居然還有一個深坑,我以為的批次插入居然不是一次插入多條資料,而是一條一條的插入...,這就有點尬了

<!-- more -->

1. 三種插入姿勢

關於專案建立以及jooq的相關使用姿勢,推薦檢視之前的博文: 【DB系列】Jooq之新增記錄使用姿勢

下面是我們採用的三種批次插入方式

/**
 * 透過Record執行批次新增
 *
 * 透過原始碼檢視,這種插入方式實際上是單條單條的寫入資料,和下面的一次插入多條有本質區別
 *
 * @param list
 * @return
 */
public boolean batchSave(List<PoetBO> list) {
    List<PoetPO> poList = list.stream().map(this::bo2po).collect(Collectors.toList());
    int[] ans = dsl.batchInsert(poList).execute();
    System.out.println(JSON.toJSONString(ans));
    return true;
}

/**
 * 類sql寫法,批次新增
 *
 * @param list
 * @return
 */
public boolean batchSave2(List<PoetBO> list) {
    InsertValuesStep2<PoetPO, Integer, String> step = dsl.insertInto(table).columns(table.ID, table.NAME);
    for (PoetBO bo : list) {
        step.values(bo.getId(), bo.getName());
    }
    return step.execute() > 0;
}

/**
 * 不基於自動生成的程式碼,來批次新增資料
 *
 * @param list
 * @return
 */
public boolean batchSave3(List<PoetBO> list) {
    InsertQuery insertQuery = dsl.insertQuery(DSL.table("poet"));
    for (PoetBO bo : list) {
        insertQuery.addValue(DSL.field("id", Integer.class), bo.getId());
        insertQuery.addValue(DSL.field("name", String.class), bo.getName());
        insertQuery.newRecord();
    }

    return insertQuery.execute() > 0;
}

請注意上面的三種批次插入方式,基本上對應的就是jooq的三種常見的用法

  • 直接藉助自動生成的Record類來操作
  • 類sql的拼接寫法,基本上我們平時的sql怎麼寫,這裡就怎麼用
  • InsertQuery:藉助jooq提供的各種Query類來執行目標操作

2. 日誌驗證

上面三種寫法中,第一種批次插入方式,並不是我們傳統理解的一次插入多條記錄,相反它是一條一條的插入的,我們可以透過開啟jooq的日誌來檢視一些執行的sql情況

配置檔案 application.properties,新增下面的配置

debug=false
trace=false
logging.level.org.jooq=DEBUG

如果有自己的logback.xml配置檔案,可以調整一下日誌級別,將jooq的debug日誌放出來

一個簡單的測試case

public void test() {
  this.batchSave(Arrays.asList(new PoetBO(14, "yh"), new PoetBO(15, "yhh")));
  this.batchSave2(Arrays.asList(new PoetBO(16, "yihui"), new PoetBO(17, "yihuihui")));
  this.batchSave3(Arrays.asList(new PoetBO(18, "YiHui"), new PoetBO(19, "YiHuiBlog")));
}

從上面的sql來看,後面兩個確實是一次插入多條,但是第一個,也沒有將具體執行的sql列印出來,所有不看原始碼的話,也沒有辦法實錘是一條一條插入的

為了驗證這個問題,一個簡單的解決辦法就是批次插入兩條資料,第一條正常,第二條異常,如果第一條插入成功,第二條失敗那就大機率是單個插入的了

// 表結構中,name的欄位最大為20,下面插入的第二條資料長度超限
try {
    this.batchSave(Arrays.asList(new PoetBO(14, "yh"), new PoetBO(15, "1234567890098765432112345")));
} catch (Exception e) {
    e.printStackTrace();
}

try {
    this.batchSave2(Arrays.asList(new PoetBO(16, "yihui"), new PoetBO(17, "1234567890098765432112345")));
} catch (Exception e) {
    e.printStackTrace();
}
this.batchSave3(Arrays.asList(new PoetBO(18, "YiHui"), new PoetBO(19, "YiHuiBlog")));

第一種批次插入失敗

第二種插入失敗

插入後結果

請注意上面的報錯,以及最終插入的結果,第一種插入方式一個插入成功一個失敗;第二種批次插入方式,兩條都插入失敗;

通常情況下,一次插入多條資料時,一個插入失敗,會導致整個插入都失敗,如下

3. 原始碼分析

上面是從日誌以及結果表現來推測實際的執行情況,接下來就需要從原始碼角度來看一下,是否真的是單個的執行了

省略掉具體的定位過程,直接找到org.jooq.impl.BatchCRUD#execute,對應的程式碼

@Override
public final int[] execute() throws DataAccessException {

    // [#1180] Run batch queries with BatchMultiple, if no bind variables
    // should be used...
    if (executeStaticStatements(configuration.settings())) {
        return executeStatic();
    }
    else {
        return executePrepared();
    }
}

上面有兩種插入方式,對於插入的核心邏輯一樣

遍歷集合,獲取單個 record,執行 CURD

II. 其他

0. 專案

系列博文

專案原始碼

1. 一灰灰Blog

盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

下面一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛

一灰灰blog

相關文章