Hibernate HQL:在同一查询中执行多个更新语句

10

我希望在Hibernate HQL中执行多个更新语句,像下面这样:

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

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

这种操作是否可行?


如果你想修改表格和数据之间的关系,是可以使用这个语句的,但是在我看来,你应该使用Hibernate Criteria而不是hql语言。参考链接:https://dev59.com/-nVC5IYBdhLWcg3wvT1a?rq=1 - Jordi Castilla
我可以在标准SQL上执行该语句。但是Hibernate会抛出异常“unexpected char: ';'”。Hibernate无法正确解析该字符串。我想知道是否有另一种语法来编写该语句或者另一种使用HQL的方式。 - Nebras
1
你将需要使用不同的 Query::executeUpdate,不能进行连接,但将在同一会话中进行事务。 - Jordi Castilla
2
@Nebras 正如我在答案中所说,这在 HQL 中是不可能的,因为 Hibernate 使用 PreparedStatement - Dragan Bozanovic
2
那么答案很简单,就是不行。如果你要执行多个类似这样的批量更新操作,使用Hibernate很少是一个好选择。为什么不直接通过纯JDBC来完成呢?而且,如果你像这样进行批量更新,数据库往返通信很少是瓶颈(除非你对每个实体进行更新/删除,应该通过更改实体状态/session.delete()来完成,并让Hibernate检测到更改并批量发出SQL)。 - Adrian Shum
显示剩余14条评论
5个回答

4
在同一个executeUpdate调用中,我想要更新Table1中的记录并从Table2中删除记录。这可能吗? executeUpdate() 执行单个更新查询。因此,不行。您必须执行与要更新/删除的表数量相同的更新查询。此外,如果将查询分开,则可以使代码更清晰:
- 查询更容易阅读,并且参数设置可读性更强,不容易出错。 - 要调试查询执行,如果单独在它们自己的 executeUpdate() 中处理查询,那么理解起来会更容易。
这并不意味着查询一定必须逐个传输。
批处理是Hibernate提供的一项功能,用于在执行多个查询时提高性能。您必须启用该功能才能使用它。hibernate.jdbc.batch_size 属性必须设置为适当的值。
如果您正在进行批处理,则需要启用JDBC批处理。如果想要实现最佳性能,这是绝对必要的。将JDBC批处理大小设置为合理的数字(例如10-50):
hibernate.jdbc.batch_size 20 如果使用标识标识符生成器,Hibernate会在JDBC级别自动禁用插入批处理。
除了官方文档之外:
然而,在您的情况下,这将是无用的,因为正如Dragan Bozanovic所解释的那样,您在查询中更新/删除不同的表。因此,它将创建与查询表相同数量的批处理执行。
因此,您应该逐个执行每个查询。只有在认为应该提交事务时才commit()。
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();

这不是批处理。请不要那么激进。 - davidxxx

3
不可能,因为Hibernate使用PreparedStatement(这很好,因为有绑定变量),而PreparedStatement不支持由多个不同语句组成的批处理。 PreparedStatement只能为一个语句批量处理不同的绑定变量组合,当在持久化上下文(会话)中刷新更改时,Hibernate用于批量插入/更新

3
简而言之,您正在寻找类似JDBC批处理的东西。Hibernate并未提供用于批量更新查询的Batching功能,我怀疑它是否会被考虑到Hibernate中。
根据我的经验,在实际生活中,HQL的批处理功能很少有用。这可能听起来很奇怪,因为在SQL+JDBC中使用批处理很有用,但在HQL中却不是这样。我会尝试解释一下。
通常,当我们使用Hibernate(或其他类似的ORM)时,我们针对实体进行操作。Hibernate将负责将实体的状态与数据库同步,这是大多数情况下JDBC批量处理可以帮助提高性能的地方。然而,在Hibernate中,我们不会通过批量更新查询更改单个实体的状态。
举个例子,伪代码如下:
在JDBC中,您可以做类似以下的事情(我试图模拟您在示例中显示的内容):
List<Order> 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<Order> 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();
    }
}

我猜想你认为你需要批处理功能,因为你正在做类似的事情(根据你的例子,你使用批量更新来更新单个记录)。然而,在Hibernate/JPA中,这不是应该采用的方式。

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

List<Order> 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批量更新旨在用于其他用途,比如一次批量更新数据库以影响大量记录,例如:
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();

在这种使用场景下,很少需要执行大量查询,因此,数据库往返的开销微不足道,因此,对于批处理支持和批量更新查询的好处很少显著。

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


1
JPA批量更新/删除生成的SQL,即调用javax.persistence.Query.executeUpdate()的语句无法通过Hibernate进行批处理传递给JDBC。@DraganBozanovic和@AdrianShum已经解释过了,但是要补充一下他们的评论:executeUpdate()返回一个int(更新或删除的实体数),无论刷新Hibernate会话与否,如何在不立即同步调用数据库的情况下返回int?JPQL/HQL/SQL必须在客户端进行评估,这是不可能的,因为要进行批量更新/删除的实体可能甚至没有被读取到Hibernate会话中。此外,如果未立即执行更新/删除,则随后查询以读取JPA实体的数据可能会变得陈旧。例如:
  1. 使用ID>1000的所有客户的executeUpdate进行批量删除。
  2. 读取ID=1001的客户实体。

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

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


0
为什么不在事务方法中分别执行这两个查询?
通过在方法上添加 @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();

        ...

 }

每个批次都开启事务并不是非常有效。 - Andremoniy
我已经在一个高级调用者服务上使用了AOP来进行事务管理。 - Nebras

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