首页 > 解决方案 > 是否可以让休眠从 postgresql 的值语句生成更新?

问题描述

给定一个 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 做过任何事情)。欢迎任何见解!


标签: postgresqlhibernatejdbcpg-jdbc

解决方案


您可以为此使用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)

推荐阅读