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

Posted

技术标签:

【中文标题】Hibernate hql,在同一个查询中执行多个更新语句【英文标题】:Hibernate hql, execute multiple update statements in same query 【发布时间】:2016-12-26 07:27:57 【问题描述】:

我想在 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 语言。 ***.com/questions/197474/… 我可以在标准 SQL 上执行语句。但是hibernate会抛出异常unexpected char: ';'。 Hibernate 无法正确解析字符串。我想知道该语句是否有另一种语法或使用 HQL 的另一种方式。 你将需要各种Query::executeUpdate,不能连接,但在会话中会是同一个事务 @Nebras 正如我在回答中所说,这对于 HQL 是不可能的,因为 Hibernate 为此使用了 PreparedStatements。 那么答案是否定的。如果您要像这样执行多次批量更新,在 Hibernate 下执行似乎很少是一个不错的选择。那你为什么不直接通过纯 JDBC 来做呢?而且,如果您要像这样进行批量更新,数据库往返很少是瓶颈(除非您正在对每个实体进行更新/删除,这应该通过更改实体状态/session.delete() 来完成,并让 Hibernate 问题检测修改并分批下发SQL) 【参考方案1】:

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

PreparedStatement 只能为一个语句批处理不同的绑定变量组合,当刷新持久性上下文(会话)中的更改时,Hibernate 将其用于batch inserts/updates。

【讨论】:

【参考方案2】:

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

这可能吗?

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

查询更易阅读,参数设置易读且不易出错。 调试查询执行,如果查询在自己的executeUpdate()中一一处理会更容易理解

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

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

hibernate.jdbc.batch_size 20 Hibernate 在 如果您使用身份标识符生成器,​​则 JDBC 级别透明。

除了来自official documentation:

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();

【讨论】:

不是批处理。请不要那么咄咄逼人。【参考方案3】:

为什么不在事务方法中分别执行两个查询

通过使用@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();

        ...

 

【讨论】:

每批开启交易不是很有效。 我已经在高级调用者服务上使用 AOP 进行事务管理。【参考方案4】:

简而言之,您所看到的类似于 JDBC 中的批处理。 Hibernate 没有为批量更新查询提供 Thich,我怀疑它是否会被考虑用于 Hibernate。

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

通常,当我们使用 Hibernate(或其他类似的 ORM)时,我们会针对实体工作。 Hibernate 将负责将实体的状态与 DB 同步,这是 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 中应该怎么做

(其实还是把持久层访问封装成一个repository比较好,这里我只是简化图)

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();

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

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

【讨论】:

【参考方案5】:

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

    执行更新以批量删除 ID > 1000 的所有客户。 读取 ID = 1001 的客户实体。

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

您要么需要在使用 JPA 时读取实体,更新它们,然后让 Hibernate 生成更新 SQL(它可以批处理),要么直接调用 JDBC 执行批处理更新。

【讨论】:

以上是关于Hibernate hql,在同一个查询中执行多个更新语句的主要内容,如果未能解决你的问题,请参考以下文章

使用 HQL 查询的 Hibernate 批量更新

无法从 Hibernate 工具执行简单的 HQL 查询

Hibernate入门---------HQL语句

Hibernate HQL基础

Hibernate HQL查询语句总结

Hibernate学习笔记 — Hibernate的查询