【问题标题】:How to batch update using jooq如何使用 jooq 批量更新
【发布时间】:2020-11-02 08:05:25
【问题描述】:

使用jOOQ使用下面的方式进行更新。

for (Balance balance : balances) {
       dslContext.update(BALANCE)
                 .set(BALANCE.AMOUNT, balance.getAmount())
                 .where(BALANCE.ID.eq(balance.getId))
                 .execute();
}

这会遍历所有余额并插入每个余额。我知道这可以使用 UpdatableRecord 来完成。但 我想避免从数据库中获取余额。 Balance 是包含超过 8 个字段的表,但我只对更新一个字段感兴趣。如果没有 UpdatableRecord,还有其他方法可以做到这一点吗?

【问题讨论】:

  • 你的(id, amount)对来自哪里?
  • 我从外部服务收到余额列表。所以对于这个方法,它是通过方法参数

标签: java jooq


【解决方案1】:

使用UpdatableRecord 的批处理语句

如果您想要DSLContext.batchUpdate() 的便利性,您仍然可以使用UpdatableRecord,而无需先从数据库中获取所有记录。假设您正在使用代码生成器并且正在生成记录,您将拥有BalanceRecord

ctx.batchUpdate(balances
   .stream()
   .map(b -> { 
       var r = new BalanceRecord();
       r.setAmount(b.getAmount());
       r.setId(b.getId());
       r.changed(BALANCE.ID, false); // Prevent setting the ID to itself
       return r;
   })
   .collect(toList()))
   .execute();

这将为您在幕后创建一个batch statement

使用BatchedConnection

从 jOOQ 3.14 开始,您可以使用 BatchedConnection 透明地批处理所有逻辑,这是一个特殊的 JDBC 连接代理,可以延迟所有 JDBC 语句(无论是否创建 jOOQ)的执行,缓冲它们直到执行是必需的:

dslContext.batched(c -> {
    for (Balance balance : balances) {
        c.dsl().update(BALANCE)
               .set(BALANCE.AMOUNT, balance.getAmount())
               .where(BALANCE.ID.eq(balance.getId))
               .execute(); // This doesn't execute the query yet
    }
} // Now, the buffered queries are being batch-executed

运行单个批量语句

从您的 cmets 看来,似乎希望将其作为单个 bulk 语句 运行,而不是在单个 batch 语句 中批处理多个语句(单次往返)。

我不相信这是正确的方法 - 语句可能会变得很大并且不一定比批处理快,但当然,您可以使用 CASE expression 来做到这一点

ctx.update(BALANCE)
   .set(BALANCE.AMOUNT, 
     case_(BALANCE.ID).mapValues(
       balances.stream().collect(toMap(balance::getId, balance::getValue))
     )
   )
   .where(BALANCE.ID.in(balances.stream().map(balance::getId).collect(toList())))
   .execute();

这将产生类似:

UPDATE balance
SET amount = 
  CASE id
    WHEN 1 THEN 2.50
    WHEN 2 THEN 3.50
    WHEN 13 THEN 8.30
  END
WHERE id IN (1, 2, 13)

根据您使用的方言,使用MERGE 可能会更好:

MERGE INTO balance
USING (
  VALUES (1, 2.50), (2, 3.50), (13, 8.30)
) AS s (i, b)
ON balance.id = s.i 
AND balance.balance = s.b
WHEN MATCHED THEN UPDATE SET balance = b

【讨论】:

  • 您能否提供一个使用 MERGE 语句/onDuplicateKeyUpdate 的示例?
  • DSLContext.batchMerge() 仅在 jOOQ 3.14 中添加(目前尚未发布)。您只需将 batchUpdate() 调用更改为 batchMerge()
  • 以上代码生成查询,该查询将仅考虑 where 子句中的 id 进行更新。如何在 where 子句中添加更多条件?
  • @pppavan:您按照Simon Martinelli 的建议编写了一个显式查询,但使用了syntax suggested in the manual for bind variable usage
  • 上述查询不会生成批量更新,而是会生成许多更新查询。 batchUpdate 的工作方式是否类似于报告的 github.com/jOOQ/jOOQ/issues/4533 中的 batchInsert
【解决方案2】:

您的代码很好,但您可以像这样使用批量更新:

List<UpdateConditionStep<BalanceRecord> updates = new ArrayList<>();
for (Balance balance : balances) {
   updates.add(dslContext.update(BALANCE)
             .set(BALANCE.AMOUNT, balance.getAmount())
             .where(BALANCE.ID.eq(balance.getId)));
}

dslContext.batch(updates).execute();

文档:https://www.jooq.org/doc/3.14/manual-single-page/#batch-execution

提示:如果您在未锁定的情况下更新余额,请小心丢失更新。

【讨论】:

  • 好像没有使用绑定值?
  • 没有?执行的 SQL 长什么样子?
  • 这对应于批处理几个不一定相关的语句。对于 JDBC,这不能使用绑定变量来完成。您正在寻找的是批处理单个查询并使用bind(...) 在同一页面上提供绑定变量集:jooq.org/doc/3.14/manual/sql-execution/batch-execution
猜你喜欢
  • 2018-09-14
  • 2018-04-21
  • 2017-07-09
  • 2021-01-20
  • 1970-01-01
  • 1970-01-01
  • 2017-11-13
  • 2012-09-21
  • 1970-01-01
相关资源
最近更新 更多