【问题标题】:Is it possible to have hibernate generate update from values statements for postgresql?是否可以让休眠从 postgresql 的值语句生成更新?
【发布时间】:2020-06-18 09:07:03
【问题描述】:

给定一个 postgresql 表

               Table "public.test"  
  Column  |            Type             | Modifiers   
----------+-----------------------------+-----------  
 id       | integer                     | not null  
 info     | text                        |   

还有以下值:

# select * from test;  
 id |     info     
----+--------------
  3 | value3        
  4 | value4
  5 | value5

您可能知道使用 postgresql,您可以使用这种语句来更新多行具有不同的值:

update test set info=tmp.info from (values (3,'newvalue3'),(4,'newvalue4'),(5,'newvalue5')) as tmp (id,info) where test.id=tmp.id; 

这会导致表在单个查询中更新为:

# select * from test;  
 id |     info     
----+--------------
  3 | newvalue3        
  4 | newvalue4
  5 | newvalue5

我一直在四处寻找如何让 hibernate 为更新查询生成这种语句。我知道如何使它适用于插入查询(使用 reWriteBatchedInserts jdbc 选项和休眠批处理配置选项)。

但是可以更新查询还是我必须自己编写本机查询? 无论我做什么,hibernate 总是向数据库发送单独的更新查询(我正在查看 postgresql 服务器语句日志以获得此确认)。

2020-06-18 08:19:48.895 UTC [1642] LOG:  execute S_6: BEGIN
2020-06-18 08:19:48.895 UTC [1642] LOG:  execute S_8: update test set info = $1 where id = $2
2020-06-18 08:19:48.895 UTC [1642] DETAIL:  parameters: $1 = 'newvalue3', $2 = '3'
2020-06-18 08:19:48.896 UTC [1642] LOG:  execute S_8: update test set info = $1 where id = $2
2020-06-18 08:19:48.896 UTC [1642] DETAIL:  parameters: $1 = 'newvalue4', $2 = '4'
2020-06-18 08:19:48.896 UTC [1642] LOG:  execute S_8: update test set info = $1 where id = $2
2020-06-18 08:19:48.896 UTC [1642] DETAIL:  parameters: $1 = 'newvalue4', $2 = '5'
2020-06-18 08:19:48.896 UTC [1642] LOG:  execute S_1: COMMIT

我总是发现发出单个大规模更新查询比针对单个行的许多单独更新快很多倍。对于许多单独的更新查询,即使它们是由 jdbc 驱动程序批量发送的,它们仍然需要由服务器顺序处理,因此它不如针对多行的单个更新查询效率高。因此,如果有人有一个不涉及为我的实体编写本机查询的解决方案,我将非常高兴!


更新

为了进一步完善我的问题,我想澄清一下。我正在寻找一种不会放弃 Hibernate 脏检查功能以进行实体更新的解决方案。对于必须更新实体列表上具有不同值的几个基本字段的一般情况,我试图避免手动编写批量更新查询。我目前正在研究休眠的 SPI,看看它是否可行。 org.hibernate.engine.jdbc.batch.spi.Batch 似乎是合适的地方,但我还不太确定,因为我从未对休眠 SPI 做过任何事情)。欢迎任何见解!


【问题讨论】:

    标签: postgresql hibernate jdbc pg-jdbc


    【解决方案1】:

    您可以为此使用Blaze-Persistence,它是基于 JPA 的查询构建器,它支持 JPA 模型之上的许多高级 DBMS 功能。

    它尚不支持 DML 中的 FROM 子句,但即将在下一个版本中实现:https://github.com/Blazebit/blaze-persistence/issues/693

    同时,您可以为此使用 CTE。首先你需要定义一个 CTE 实体(Blaze-Persistence 的概念):

    @CTE
    @Entity
    public class InfoCte {
      @Id Integer id;
      String info;
    }
    

    我假设您的实体模型大致如下所示

    @Entity
    public class Test {
      @Id Integer id;
      String info;
    }
    

    然后你可以像这样使用 Blaze-Persistence:

    criteriaBuilderFactory.update(entityManager, Test.class, "test")
      .with(InfoCte.class, false)
        .fromValues(Test.class, "newInfos", newInfosCollection)
        .bind("id").select("newInfos.id")
        .bind("info").select("newInfos.info")
      .end()
      .set("info")
        .from(InfoCte.class, "cte")
        .select("cte.info")
        .where("cte.id").eqExpression("test.id")
      .end()
      .whereExists()
        .from(InfoCte.class, "cte")
        .where("cte.id").eqExpression("test.id")
      .end()
    .executeUpdate();
    

    这将创建一个类似于以下的 SQL 查询

    WITH InfoCte(id, info) AS(
      SELECT t.id, t.info
      FROM (VALUES(1, 'newValue', ...)) t(id, info)
    )
    UPDATE test
    SET info = (SELECT cte.info FROM InfoCte cte WHERE cte.id = test.id)
    WHERE EXISTS (SELECT 1 FROM InfoCte cte WHERE cte.id = test.id)
    

    【讨论】:

    • 感谢您的详细解答。但是,恐怕它并不能完全解决我的问题,因为我仍然需要使用 blaze 持久性查询生成器编写查询。我应该在我的问题中说明我正在寻找一种方法来使用“cte”或“从值更新”更新查询,同时仍然利用休眠脏检查功能,而无需为每个更新案例编写自定义查询。我目前正在研究 hibernate SPI org.hibernate.engine.jdbc.batch.spi.Batch 以了解它是否可行,但我不熟悉如何深入研究 Hibernate 内部,所以我很迷茫
    • 所以您希望以这种方式刷新实体?我有点怀疑这会给你你想要的性能。您可能想要的是 JDBC 批处理:baeldung.com/jpa-hibernate-batch-insert-update
    • 我已经在使用 jdbc 批处理进行插入、更新和删除。对于插入,它工作得很好,因为 pgjdbc 在发送之前将它们重写在一个语句中。但是对于更新或删除,它不提供这样的功能。您不认为单个更新查询(在我的情况下每行具有不同的更新值)会比多个(例如 200 个批次)更新查询更快,即使它们是在一个批次中发送的?
    • 我不认为带有文字或参数的单个语句会更好,因为这通常对解析器来说需要更多的工作。您可以自己尝试一下,我很确定您会发现发送大数据批次的单个准备好的语句会执行得更好。
    • 这实际上是一个反问的问题。在我的情况下,使用数百行的不同值更新几个字段实际上更快。我已经做了一些基准测试,在一个 1M 行的表中更新一批 1000 个随机行平均比在一个 jdbc 批处理中发送的多个更新语句快 3 到 4 倍。我知道我的基准测试可能没有被普遍化,但我只想说我不会在没有首先查看的情况下研究这个问题,它实际上是更快地执行具有多个值的单次更新。
    猜你喜欢
    • 2017-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-11
    • 1970-01-01
    • 1970-01-01
    • 2016-03-02
    • 2017-09-06
    相关资源
    最近更新 更多