JPA CriteriaBuilder调用jsonb_path_exists实现JSON数组条件查询
JPA CriteriaBuilder 实现PostgreSQL JSONB数组过滤方案
问题描述
使用JPA CriteriaBuilder实现数据过滤时,普通日期过滤逻辑可正常运行,但处理存储JSONB数组类型的attributes列时遇到问题:需要筛选出attributes数组中包含{"key":"market","value":"australia"}键值对的记录,对应原生SQL可正确返回id为1、3的记录。
测试表结构与数据
| id | attributes | date |
|---|---|---|
| 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
相关产品推荐
相关产品推荐

