使用Spring data jpa和CriteriaQuery在jsonb列中进行搜索操作

问题描述 投票:0回答:1

我有一个 Postgres SQL 查询,它将在 jsonb 类型列中定义的键的值数组内执行搜索操作。

SELECT o.id
FROM customer c INNER JOIN order o
on c.id = o.c_id where EXISTS (
    SELECT 1
    FROM jsonb_array_elements_text(c.customer_data->'sid') AS value
    WHERE value = '3456' OR value='89110'
); 

在这里, sid 是我的密钥并且具有值数组。给定的查询将在值数组内执行深度搜索,如果键与这些值中的任何一个匹配,则返回记录。

我想在 spring data jpa 中生成其等效项,它将使用条件生成器和条件查询返回相同的结果。有关于此的任何建议都会有帮助吗?

我的两个实体都以一对多关系连接,我正在尝试实现上述查询结果:

public List<Long> findCustomerIds(EntityManager entityManager,String key, List<String> sidValues) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Long> cq = cb.createQuery(Long.class);
    Root<Customer> cRoot = cq.from(Customer.class);
    
    // Join between Customer and Order
    Join<Customer, Order> oJoin = cRoot.join("orders");

    // Subquery to handle JSONB array elements condition
    Subquery<String> subquery = cq.subquery(String.class);
    Root<Customer> subRoot = subquery.from(Customer.class);
    Path<Object> subCustomerDataPath = subRoot.get("customerData");
    Path<String> subSidPath = cb.function("jsonb_array_elements_text", String.class, subCustomerDataPath.get(key));

    subquery.select(subRoot.get("id"))
            .where(cb.or(
                sidValues.stream()
                              .map(sid -> cb.equal(cb.upper(subSidPath), sid.toUpperCase()))
                              .toArray(Predicate[]::new)
            ));

    cq.select(cRoot.get("id"))
      .where(cb.equal(oJoin.get("customer"), cRoot), cb.exists(subquery));

    return entityManager.createQuery(cq).getResultList();
}

问题出在代码的给定部分:

// Subquery to handle JSONB array elements condition
Subquery<String> subquery = cq.subquery(String.class);
Root<Customer> subRoot = subquery.from(Customer.class);
Path<Object> subCustomerDataPath = subRoot.get("customerData");
Path<String> subSidPath = cb.function("jsonb_array_elements_text", String.class, subCustomerDataPath.get(key));
subquery.select(subRoot.get("id"))
.where(cb.or(
sidValues.stream()
   .map(sid -> cb.equal(cb.upper(subSidPath), sid.toUpperCase()))
                              .toArray(Predicate[]::new)
));

我在子查询部分收到非法尝试取消引用路径源[null.customerData]错误。我没有明白我的代码有什么问题。

作为参考,我在实体中以以下方式定义了 jsonb 字段,因为所有键和值都以以下方式存储:

@Type(type = "jsonb")
@Column(name = "customer_data",columnDefinition = "jsonb")
private Map<String,List<String>> customerData = new HashMap<>();

如果您有任何替代方法或任何建议都会有所帮助。预先感谢您的帮助。

postgresql spring-boot hibernate spring-data-jpa hibernate-criteria
1个回答
0
投票

与 Hibernate Criteria 中的函数一起应用时,jsonb_array_element_text 表达式存在一些问题。

给定的查询可以用作上述查询的替代,以获得相同的结果:

SELECT o.id
FROM customer c INNER JOIN order o
on c.id = o.c_id where LIKE c.customer_data->'sid' LIKE '%"3456"%' OR c.customer_data->'sid' LIKE '%"89110"%'

注意:内部存储的数组值将被转换为字符串并对字符串执行搜索操作。 如果值是字符串数组,请确保传递引号,否则它将无法按预期工作。

要使用休眠标准实现相同的功能,请使用 jsonb_extract_path_text 函数。

public List<Long> findCustomerIds(EntityManager entityManager,String key, List<String> sidValues) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Long> cq = cb.createQuery(Long.class);
    Root<Customer> cRoot = cq.from(Customer.class);
    
    // Join between Customer and Order
    Join<Customer, Order> oJoin = cRoot.join("orders");

    // Constructing the WHERE clause predicates
    Predicate[] predicates = new Predicate[values.size()];
    for (int i = 0; i < values.size(); i++) {
predicates[i]= cb.like(cb.function("jsonb_extract_path_text", String.class, subEntityJoin.get("customerData"), cb.literal(key)),"%\"" + values.get(i) + "\"%");
 }

    // Applying OR condition
    requestPredicate= cb.or(predicates);

    cq.select(cRoot.get("id")).where(requestPredicate);


    return entityManager.createQuery(cq).getResultList();
}

如果值存储在字符串格式列表中,则在外部传递要搜索的值的引号,否则它将按预期工作。

© www.soinside.com 2019 - 2024. All rights reserved.