Spring JPA Criteria查询字符串数组字段报类型错误问题排查
如何为字符串数组类型实体编写Criteria API查询语句
问题场景
映射PostgreSQL text[]类型的实体定义如下:
@Table(name = "Test") public class Test { @Type(type = "string-array") @Column(name = "tempCol", columnDefinition = "text[]") public List<String> sampleColumnList; }
当前编写的查询逻辑如下,预期查询tempCol字段中包含abc、def值的记录:
// value = {"abc", "def"} private Predicate getPredicate(List<String> value, EntityManager e) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<SampleColumn> cq = cb.createQuery(SampleColumn.class); Root<SampleColumn> root = cq.from(SampleColumn.class); Object[] strArray1 = new Object[] {value}; Expression<Object[]> exp = root.get("tempCol"); Predicate predicate = cb.and(exp.in(strArray1)); cq.select(root).where(predicate); TypedQuery<Test> tp = entityManager.createQuery(cq); List<Test> c = tp.getResultList(); }
运行代码持续抛出异常:
The provided abc is not Object[] or List!
尝试直接用List替代Object[]传参后依然无法正常运行。
异常栈截图
问题根因
- 语法语义不匹配:
exp.in(xxx)的逻辑是判断表达式本身的值是否存在于传入的集合中,也就是判断整个数组字段是否完全等于传入的某一个元素,完全不符合「数组字段包含指定元素」的查询需求。 - 参数构造错误:
new Object[] {value}是将整个List<String>对象作为数组的唯一元素传入,框架做类型校验时,会尝试将List元素和数据库数组的字符串元素做匹配,直接触发类型不匹配异常。 - 属性名引用错误:
root.get()方法需要传入实体类的属性名,代码里写的tempCol是数据库列名,实体中对应的属性名是sampleColumnList,本身就存在映射错误。 - JPA标准API没有原生支持PostgreSQL数组类型的包含运算,
string-array是Hibernate Types第三方库提供的类型映射,这类数据库特殊类型的查询不能直接套用基础Criteria方法,需要调用PostgreSQL原生的数组操作符实现。
正确实现方案
PostgreSQL中判断数组是否包含所有指定元素的操作符为@>,结合CriteriaBuilder的自定义函数能力实现查询即可:
private Predicate getPredicate(List<String> value, EntityManager entityManager) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Test> cq = cb.createQuery(Test.class); Root<Test> root = cq.from(Test.class); // 调用数组包含函数,判断字段数组是否包含传入的所有参数 Predicate predicate = cb.isTrue( cb.function( "array_contains", Boolean.class, root.get("sampleColumnList"), cb.literal(value.toArray(new String[0])) ) ); cq.select(root).where(predicate); TypedQuery<Test> tp = entityManager.createQuery(cq); List<Test> result = tp.getResultList(); return predicate; }
如果当前Hibernate版本没有注册array_contains函数,可以直接通过OPERATOR关键字调用原生操作符:
Predicate predicate = cb.isTrue( cb.function( "OPERATOR", Boolean.class, cb.literal("@>"), root.get("sampleColumnList"), cb.literal(value.toArray(new String[0])) ) );
扩展说明
- 如果需求是匹配数组中包含任意一个传入值,将操作符替换为
&&即可。 - 传参时必须将Java的
List<String>转为和数据库字段类型匹配的String[]数组,避免类型转换异常。
内容的提问来源于stack exchange,提问作者s_91
相关产品推荐
相关产品推荐

