如何根据postgres的jsonb列的where条件(无本机查询)使用jpa在spring boot中选择数据?

Posted

技术标签:

【中文标题】如何根据postgres的jsonb列的where条件(无本机查询)使用jpa在spring boot中选择数据?【英文标题】:How to select data in spring boot using jpa based on where condition of jsonb column of postgres(without native query)? 【发布时间】:2019-07-27 22:44:39 【问题描述】:

我有带有 jsonb 字段的 Postgres 数据库。我在 jpa 中使用了条件生成器的谓词列表作为条件。现在,我想获取存储在基于多个 where 原因以及 JSON 的数据库中的 JSON 数据。怎么可能呢?

Database table

private List<Predicate> whereClause(CriteriaBuilder criteriaBuilder, Root<KafkaLog> kafkaLog,
  KafkaLogSearchDto search) 
List<Predicate> predicates = new ArrayList<>();
if (search.getTopic() != null && !search.getTopic().isEmpty()) 
  Expression<String> literal =
      criteriaBuilder.literal("%" + search.getTopic().toUpperCase() + "%");
  predicates.add(criteriaBuilder.like(criteriaBuilder.upper(kafkaLog.get("topic")), literal));

if (search.getOffset() != null) 
  predicates.add(criteriaBuilder.equal(kafkaLog.get("offset"), search.getOffset()));

if (search.getResourceId() != null) 
  predicates.add(criteriaBuilder.equal(kafkaLog.get("invoiceId"), search.getResourceId()));

if (search.getPartition() != null) 
  predicates.add(criteriaBuilder.equal(kafkaLog.get("partition"), search.getPartition()));

if (search.getStatus() != null) 
  predicates.add(criteriaBuilder.equal(kafkaLog.get("status"), search.getStatus()));

if (search.getCreatedAtFrom() != null && search.getCreatedAtTo() != null) 
  predicates.add(criteriaBuilder.and(
      criteriaBuilder.greaterThanOrEqualTo(kafkaLog.get("createdAt").as(Date.class),
          search.getCreatedAtFrom()),
      criteriaBuilder.lessThanOrEqualTo(kafkaLog.get("createdAt").as(Date.class),
          search.getCreatedAtTo())));

if (search.getPayload() != null && !search.getPayload().isEmpty()) 
  Expression<String> literal =
      criteriaBuilder.literal("%" + search.getPayload().toUpperCase() + "%"); 




return predicates;

private CriteriaQuery<KafkaLog> getKafkaBySearchCriteria(KafkaLogSearchDto search, String orderBy,
  int sortingDirection) 
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<KafkaLog> criteriaQuery = criteriaBuilder.createQuery(KafkaLog.class);
Root<KafkaLog> kafkaLog = criteriaQuery.from(KafkaLog.class);

List<Predicate> predicates = whereClause(criteriaBuilder, kafkaLog, search);
criteriaQuery.where(predicates.toArray(new Predicate[] ));

if (orderBy == null || orderBy.isEmpty()) 
  orderBy = "topic";

Expression<?> orderColumn = kafkaLog.get(orderBy);
if (orderColumn != null) 
  if (sortingDirection == 0) 
    criteriaQuery.orderBy(criteriaBuilder.asc(orderColumn));
   else if (sortingDirection == 1) 
    criteriaQuery.orderBy(criteriaBuilder.desc(orderColumn));
  

return criteriaQuery;

【问题讨论】:

【参考方案1】:

显然,对 JDBC/数据库级别的自定义数据类型支持并不是 JPA 本身的一部分。对于仅读取 JSON,您可能可以使用到 String 的实体映射来完成它(如果 JPA 实现允许这样做)。

但是,您有一些选择:

    如果您需要坚持使用 JPA,您可以实现一个转换器来为您处理 JSON(本文中的通用或更具体 - 随您的喜好):Using JPA with PostgreSQL JSON 如果可以的话,放弃 JPA 并直接使用 JDBC/SQL 来实现,这可以让您充分发挥 PostgreSQL 的潜力:how to store PostgreSQL jsonb using SpringBoot + JPA?

在读取 JSONB 字段时,您可以让数据库使用显式类型转换来转换为字符串(因为 JDBC 中还没有直接的 JSON 支持),例如:

SELECT payload::text FROM my_table WHERE ...

...或者对 JSON 字段本身进行一些花哨的过滤,或者只返回一部分 JSON 数据,aso。

【讨论】:

感谢 Ancoron 的回复。正如您所建议的那样,我已经尝试将其转换为字符串,但是对于 Rest 调用来说太耗时了。在数据库中搜索大约需要 45-50 秒。因此,我在这种情况下变得束手无策。此外,我不能对特定的有效负载列使用 nativequery。 好的,那么我们需要查看 JPA 实现从条件查询中生成了哪个实际 SQL,然后您应该执行 EXPLAIN (ANALYZE, BUFFERS) &lt;your-sql-here&gt; 并编辑您的问题(查询太慢?)或为此创建一个新问题。在我看来,你已经让它在功能上正常工作了,只是它太慢了,对吧?【参考方案2】:

这是使用 like 子句搜索 jsonb 的示例谓词:

predicate.getExpressions().add(cb.and(cb.like(cb.function("jsonb_extract_path_text", String.class, root.get("tags"), cb.literal(this.key))), "%"+ this.value + "%" )));

【讨论】:

以上是关于如何根据postgres的jsonb列的where条件(无本机查询)使用jpa在spring boot中选择数据?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据 jsonb 列中的 unix 整数设置 postgres 时间戳列?

结合关系查询提高 Postgres jsonb 查询的性能

jsonb内部字段上的Postgres GROUP BY

Postgres:查询多个jsonb字段

Postgres:从匿名jsonb数组元素中删除对象

如何在 Postgres 中选择一列 json 对象,以便返回的行是 json 数组?