JPA - 批量/批量更新 - 哪种方法更好?

12
我发现JPA不支持以下更新操作:
Update Person p set p.name = :name_1 where p.id = :id_1,
                    p.name = :name_2 where p.id = :id_2,
                    p.name = :name_3 where p.id = :id_3
                    .... 
                 // It could go on, depending on the size of the input. Could be in 100s

所以我有两个选择:

选择1:

Query q = em.createQuery("Update Person p set p.name = :name where p.id = :id");

For ( int x=0; PersonsList.length; x++ ) {
      // add name and id parameters
      em.executeUpdate(); 
}

问题:

  1. 这就是批量更新所需的全部内容吗?还需要添加其他内容吗? 我设置了hibernate.jdbc.batch_size", "20"
  2. 默认情况下是否启用了乐观锁定?(虽然我的实体中没有 @Version)
  3. 如果没有@Version,我需要做什么才能强制执行乐观锁定?

选项2:

使用Select Case 语法或 Criteria API构造一个单一查询

问题:

  1. 这里是否仍然发生批处理?(在一个大查询中)
  2. 在性能方面,这是否比第一种方法更好?
  3. 这两种方法中哪种是推荐的方法?有没有更好的方法?

你真的想为3行运行批量操作吗?批量操作会在目标表上引起意向锁,这不是一个好的选择。你只需要通过em.begin()开始批处理,然后调用每个DML,最后使用em.commit();提交即可。 - user2511414
我们使用Oracle。这是一个Web服务调用。 - Kevin Rave
如果该过程干扰了您的系统,请选择使用本机Oracle“bulk collect and forall”;否则,使用带有事务的选项一可能会有所帮助。另外,建议您按每个批量操作拆分目标表,这将确保BX仅持有所需部分而非整张表格。就像我之前说过的,这取决于情况。 - user2511414
我的问题是:使用选项1,它是否会“自动批量”更新?还是我需要进行一些配置或类似的操作? - Kevin Rave
1
好的,伙计,它会自动设置批处理大小,并且由于您将其设置为20,因此每个批次将有20个调用,但不建议用于重负载,通常batch_size值在10到35之间(取决于情况),对于非常重的负载,请使用数据库优先方式 :) - user2511414
显示剩余2条评论
3个回答

6
在你的问题标题中,你提到了Bulk Update和Delete,但实际上这次你需要批处理。
当你想要更新/删除所有符合WHERE子句所表示的过滤条件的行时,需要使用Bulk Update and Delete
在这里,你需要JDBC批量更新。你需要设置以下配置属性:
<property name="hibernate.jdbc.batch_size" value="50"/>

如果你这样做,你可以简单地更新实体,Hibernate会为你批处理UPDATE语句。 选项1并不是很有用,因为它会生成无法批处理的N个UPDATE语句。 选项2也不是很有用,因为它将生成非常复杂的查询,其执行计划可能比在一个简单的批处理UPDATE语句中执行所有操作更加复杂。
所以,按照以下方式操作:
  1. 使用分页获取实体
  2. 使用Hibernate更新它们,并让它为您批量更新。
如果您有许多这样的实体,请使用查询分页

当使用批量更新时,Hibernate不会自动递增用于乐观锁定的version列,就像对于常规实体更新一样,因此您需要显式递增version实体属性


5
您可以在不遍历整个集合的情况下更新对象列表。
List<Integer> demo = Arrays.asList(1,2,3,4);
final String update = "UPDATE User u SET u.enabled = true WHERE u.id IN (?1)";

return super.getEntityManager()
    .createQuery(update)
    .setParameter(1, ids)
    .executeUpdate();

1
我们如何更新一些ID的u.enabled属性为true,而另外一些ID的u.enabled属性为false呢? - PAA
我认为最好的方法是进行两次更新,避免使用复杂的解决方案。 - OJVM
1
super.getEntityManager()是什么?它与PersistantContext中的EntityManager类对象有何不同? - user20072008
嗨,在这种情况下,使用super是因为它来自一个继承类,该类具有以下代码 private EntityManager entityManager; public EntityManager getEntityManager(){ return this.entityManager; } - OJVM

2
如果您要使用批处理,请参阅hibernate文档中的本章节
batch_size更多地是为了内存优化而不是查询优化,查询保持不变,但您还可以通过充分利用内存来最小化往返次数。您需要根据batch_size的设置每N次执行一次flush()和clear()。
但仍然 . .
在1个语句中进行更新比在多个语句中进行更新要快得多,因此如果您:
- 可以简单地循环并在SQL中执行 - 不需要级联更新到其他实体 - 在更新多个值时确实需要更快的性能 - 不需要其他HQL优势,如预防SQL注入的准备语句
那么您可以考虑创建本地查询而不是HQL。

但是,如果我使用更新本地查询,那么我不会得到“批量更新”模式吗?有人告诉我要使用批量更新。所以我想知道最好的方法是什么。 - Kevin Rave
正如我所说,batch_size只是内存管理的问题,因此您可以在一个提交中提交数万行而不会出现OutOfMemoryException。因此,如果您的本地查询可以在没有OutOfMemoryException的情况下完成所需的操作,则可以考虑使用它,但如果不能,则应改用HQL。但我认为SQL不会出现OutOfMemoryException问题,因为Hibernate只将持久化对象保存到会话级缓存中。 - Angga
@Angga 在EntityManager下,本地查询不支持批处理。您必须获取连接句柄,然后使用PreparedStatement才能使用批处理API。 - TheRealChx101

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接