Hibernate hql,在同一个查询中执行多个更新语句

我想在hibernate Hql中的同一个查询中执行多个更新语句。 如下:

hql = " update Table1 set prob1=null where id=:id1; " + " delete from Table2 where id =:id2 "; ... query.executeUpdate(); 

在同一个executeUpdate调用中,我想更新Table1中的记录并从Table2中删除记录。

那可能吗?

简而言之,您所看到的就像在JDBC中进行批处理一样。 Hibernate没有为Bulk Update查询提供Thich,我怀疑它是否会被考虑用于Hibernate。

根据我过去的经验,HQL的批处理function在现实生活中很少有用。 某些东西在SQL + JDBC中有用但在HQL中却没有用,这听起来很奇怪。 我会尽力解释。

通常当我们使用Hibernate(或其他类似的ORM)时,我们会对实体起作用。 Hibernate将负责将我们的实体状态与DB同步,这是JDBC批处理可以帮助提高性能的大多数情况。 但是,在Hibernate中,我们不会通过批量更新查询更改单个实体的状态。

举个例子,用伪代码:

在JDBC中,您可以执行类似的操作(我试图模仿您在示例中显示的内容):

 List orders = findOrderByUserId(userName); for (Order order: orders) { if (order outstanding quantity is 0) { dbConn.addBatch("update ORDER set STATE='C' where ID=:id", order.id); } else if (order is after expriation time) { dbConn.addBatch("delete ORDER where ID=:id", order.id); } } dbConn.executeBatch(); 

从JDBC逻辑到Hibernate的朴素转换可能会给你这样的东西:

 List orders = findOrderByUserId(userName); for (Order order: orders) { if (order outstanding quantity is 0) { q = session.createQuery("update Order set state='C' where id=:id"); q.setParameter("id", order.id); q.executeUpdate(); } else if (order is after expriation time) { q = session.createQuery("delete Order where id=:id"); q.setParameter("id", order.id); q.executeUpdate(); } } 

我怀疑你认为你需要批处理function,因为你正在做类似的事情(根据你的例子,你使用个人记录的批量更新)。 然而,在Hibernate / JPA中应该怎么做

(实际上最好通过存储库包装持久层访问,这里我只是简化了图片)

 List orders = findOrderByUserId(userName); for (Order order: orders) { if (order.anyOutstanding()) { order.complete(); // which internally update the state } else if (order.expired) { session.delete(order); } } session.flush(); // or you may simply leave it to flush automatically before txn commit 

通过这样做,Hibernate足够智能地检测已更改/删除/插入的实体,并利用JDBC批处理在flush()执行DB CUD操作。 更重要的是,这是ORM的全部目的:我们希望提供与行为相关的实体,实体的内部状态更改可以“透明地”反映在持久存储中。

HQL批量更新旨在用于其他用途,这类似于对DB进行批量更新以影响大量记录,例如:

 q = session.createQuery("update Order set state='C' " + " where user.id=:user_id " + " and outstandingQty = 0 and state != 'C' "); q.setParameter("user_id", userId); q.executeUpdate(); 

在这种使用场景中很少需要执行大量查询,因此,DB往返的开销是微不足道的,因此,对批量更新查询的益处和批处理支持很少是重要的。

我不能忽略有些情况下您确实需要发出大量更新查询,这些查询不适合由有意义的实体行为完成。 在这种情况下,您可能需要重新考虑Hibernate是否是正确使用的工具。 您可以考虑在此类用例中使用纯JDBC,以便您可以控制如何发出查询。

不,这是不可能的,因为Hibernate为此使用了PreparedStatement (这是因为绑定变量很好),而PreparedStatement不支持由多个不同语句组成的批处理。

PreparedStatement只能为一个语句批量绑定变量的不同组合,Hibernate在刷新持久化上下文(会话)中的更改时用于批量插入/更新 。

在同一个executeUpdate调用中,我想更新Table1中的记录并从Table2中删除记录。

那可能吗?

executeUpdate()执行单个更新查询。 所以,不,你不能这样做。 您必须执行与表一样多的更新查询才能更新/删除。 此外,如果您分开查询,它会使代码更清晰:

  • 查询更易于阅读,参数设置可读且不容易出错。
  • 调试查询执行,如果在自己的executeUpdate()逐个处理查询,将更容易理解

这并不意味着必须逐个传输查询。
批处理是Hibernate提供的一项function,可在您想要执行多个查询时提高性能。 您必须启用该function才能使用它。 必须使用合适的值设置hibernate.jdbc.batch_size属性。

如果您正在进行批处理,则需要启用JDBC批处理。 如果您想获得最佳性能,这绝对是必不可少的。 将JDBC批处理大小设置为合理的数字(例如,10-50):

hibernate.jdbc.batch_size 20如果使用身份标识符生成器,​​Hibernate会透明地禁用JDBC级别的插入批处理。

除官方文件外 :

如果您使用身份标识符生成器,​​Hibernate将透明地禁用JDBC级别的插入批处理。

然而,在你的情况下,它将是无用的,因为Dragan Bozanovic解释你更新/删除查询中的不同表。 因此,它将创建与查询表一样多的批处理执行。
因此,您应该单独执行每个查询。 只要在你认为应该是:

 hql = "update Table1 set prob1=null where id=:id1;" ... query.setParameter("id1",...); query.executeUpdate(); hql = "delete from Table2 where id =:id2"; ... query.executeUpdate(); query.setParameter("id2",...); .. tx.commit(); 

JPA批量更新/删除时生成的SQL,即调用javax.persistence.Query.executeUpdate()在传递给JDBC时不能由Hibernate批处理。 @DraganBozanovic和@AdrianShum已经解释了这一点,但是要添加他们的注释:executeUpdate()返回一个int(更新或删除的实体数) – 无论刷新Hibernate会话,如何在不调用数据库的情况下返回int立即和同步? 必须在客户端评估JPQL / HQL / SQL,这是不可能的,因为要批量更新/删除的实体可能甚至没有被读入Hibernate会话。 此外,如果未立即在数据库上执行更新/删除,则在JPA实体中读取的后续查询可能会获得过时数据。 例:

  1. executeUpdate批量删除ID> 1000的所有客户。
  2. 读取ID = 1001的客户实体。

如果允许将1的executeUpdate推迟到读取2之后,则得到错误的答案(客户仍然存在)。

您需要使用JPA读取实体,更新它们,让Hibernate生成更新SQL(它可以批量处理),或者直接调用JDBC来执行批量更新。

为什么不在Transactional方法中单独执行这两个查询

通过使用@Transactional注释方法,如果任何查询失败,则另一个不会执行。

  @Transactional(propagation = Propagation.REQUIRED, readOnly = false) public void executeQuery(Obj1 obj1) { String query="update table1 set actualRepaymentAmount=expectedRepaymentAmount,active='Y' where loanCaseId = '"+caseId+"'"; sessionFactory.getCurrentSession().createQuery(query).executeUpdate(); query="update table2 set loanStatus='C' where loanCaseId = '"+caseId+"'"; sessionFactory.getCurrentSession().createQuery(query).executeUpdate(); ... }