You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JPA CriteriaBuilder调用jsonb_path_exists实现JSON数组条件查询

JPA CriteriaBuilder 实现PostgreSQL JSONB数组过滤方案

问题描述

使用JPA CriteriaBuilder实现数据过滤时,普通日期过滤逻辑可正常运行,但处理存储JSONB数组类型的attributes列时遇到问题:需要筛选出attributes数组中包含{"key":"market","value":"australia"}键值对的记录,对应原生SQL可正确返回id为1、3的记录。

测试表结构与数据

idattributesdate
1[{"key": "market", "value": "australia"}, {"key": "language", "value": "polish"}]2022-05-24 17:30:04.046000
2[{"key": "country", "value": "australia"}, {"key": "language", "value": "polish"}]2022-05-24 17:30:04.046000
3[{"key": "market", "value": "australia"}, {"key": "language", "value": "polish"}]2022-05-24 17:30:04.046000
4[{"key": "market", "value": "brazil"}, {"key": "language", "value": "polish"}]2022-05-24 17:30:04.046000
5[{"key": "market", "value": "brazil"}, {"key": "language", "value": "australia"}]2022-05-24 17:30:04.046000

验证通过的原生SQL

SELECT * FROM run WHERE jsonb_path_exists("attributes", '$[*] ? ((@.key == "market") && (@.value == "australia"))')

已正常运行的日期过滤代码

public static Specification<Run> andGreaterThanFromDate(Specification<Run> specification, LocalDateTime fromDate) {
    return specification.and((Root<Run> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> cb
        .greaterThanOrEqualTo(root.get(LAUNCH_START_DATE), fromDate));
}   

错误尝试与报错

尝试编写JSON过滤逻辑调用jsonb_path_exists函数时,执行抛出错误:org.hibernate.QueryException: unexpected char: '@',错误实现代码如下:

public static Specification<Run> andDynamicAttributeContains(Specification<Run> specification) {
    return specification.and((Root<Run> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> cb.isTrue(
        cb.function(
            "jsonb_path_exists",
            Boolean.class,
            cb.parameter(Path.class, "launch_attributes"),
            cb.parameter(Boolean.class, "$[*] ? ((@.key == \"market\") && (@.value == \"australia\"))"))));
  }

错误原因

  • 用法错误:cb.parameter()是用来定义查询入参占位符的方法,第二个入参是参数名而非参数值,不能用来引用实体映射的表字段,也不能直接传入JSON路径表达式。错误代码中既没有正确引用attributes列,也将JSON路径误作为参数名传入,Hibernate解析JPQL时会尝试解析该位置的字符串,遇到@特殊字符直接抛出解析异常。
  • 类型不匹配:第二个参数定义为Boolean.class类型,实际需要传入的是字符串类型的JSON路径表达式,类型定义完全错误。

正确实现代码

修正逻辑:直接通过root.get()引用实体类中映射attributes列的属性,用cb.literal()包装JSON路径字符串作为常量传入,避免Hibernate提前解析路径中的特殊字符,支持动态传入键值参数:

public static Specification<Run> andDynamicAttributeContains(Specification<Run> specification, String attrKey, String attrValue) {
    return specification.and((Root<Run> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
        // 直接引用实体映射的attributes列,根据实际属性名修改字符串
        Expression<?> attrColumn = root.get("attributes");
        // 构造JSON路径表达式,用literal包装为SQL常量,避免Hibernate解析特殊字符
        String jsonPathExpr = String.format("$[*] ? (@.key == \"%s\" && @.value == \"%s\")", attrKey, attrValue);
        Expression<String> pathLiteral = cb.literal(jsonPathExpr);
        // 调用PG的jsonb_path_exists函数
        Expression<Boolean> matchExpr = cb.function(
                "jsonb_path_exists",
                Boolean.class,
                attrColumn,
                pathLiteral
        );
        return cb.isTrue(matchExpr);
    });
}

注意事项

  • 请根据实体类中attributes字段的实际映射类型调整attrColumn的泛型,如果是Hibernate 5环境,字段需要加@Type(type = "jsonb")注解映射JSONB类型;Hibernate 6/JPA3环境加@JdbcTypeCode(SqlTypes.JSON)即可,无需额外类型转换。
  • 调用方法时传入attrKey = "market", attrValue = "australia",生成的SQL与原生SQL完全一致,可正确返回id为1、3的记录,不会再出现特殊字符解析错误。

内容的提问来源于stack exchange,提问作者arturPabjanczyk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 09:27:34