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

Hibernate hql, execute multiple update statements in same query

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

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

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

这可能吗?

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

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

in the same executeUpdate call I want to update records in Table1 and delete records from Table2.

Is that possible?

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

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

并不是说查询一定要一一传送。
批处理是 Hibernate 提供的一项功能,可在您要执行多个查询时提高性能。您必须启用该功能才能使用它。 hibernate.jdbc.batch_size 属性 必须设置合适的值。

If you are undertaking batch processing you will need to enable the use of JDBC batching. This is absolutely essential if you want to achieve optimal performance. Set the JDBC batch size to a reasonable number (10-50, for example):

hibernate.jdbc.batch_size 20 Hibernate disables insert batching at the JDBC level transparently if you use an identity identifier generator.

除了official documentation

Hibernate disables insert batching at the JDBC level transparently if you use an identity identifier generator.

然而,在您的情况下,它将毫无用处,因为正如 Dragan Bozanovic 向您解释的那样 update/delete 您的查询中有不同的表。因此,它会创建与查询表一样多的批处理。
因此,您应该单独执行每个查询。当你认为它应该是:

时,只需提交()交易
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();

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

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

        ...

 }

简而言之,您正在寻找的是 JDBC 中的批处理。 Hibernate 没有为批量更新查询提供 Thich,我怀疑它是否会被 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();
    }
}

我怀疑您认为您需要批处理功能是因为您正在做类似的事情(根据您的示例,您对单个记录使用批量更新)。但是 NOT 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 足够智能来检测 changed/deleted/inserted 个实体,并利用 JDBC 批处理在 flush() 处执行 DB CUD 操作。更重要的是,这就是 ORM 的全部目的:我们希望提供行为丰富的实体供其使用,为此实体的内部状态变化可以 "transparently" 反映在持久存储中。

HQL Bulk Update 旨在用于其他用途,这类似于对 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();

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

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

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

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

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

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