Spring 2.7迁移至3.0.6:Specification子查询In语句报错求助
使用QueryBuilder构建复杂Specification查询,通过repository.findAll(Specification<...>)实现结果过滤。该查询在Spring 2.7中运行正常,但迁移至Spring 3.0.6后失效。注释掉builder.in(equip.get("id")).value(subqueryEquip),这一行后查询可正常执行,但该语句是必要的过滤条件。
控制台抛出的错误信息:
class org.hibernate.metamodel.mapping.internal.BasicAttributeMapping
cannot be cast to class
org.hibernate.metamodel.mapping.EntityValuedModelPart
(org.hibernate.metamodel.mapping.internal.BasicAttributeMapping and
org.hibernate.metamodel.mapping.EntityValuedModelPart are in unnamed
module of loader 'app')
推测是Subquery结合In语句的写法在新版本中出现问题,相关代码如下:
Specification生成函数
public static Specification<Usuari> filterByLaboratorisAndEquipsAssignatsToUser(UsuariFilterParams usuariFilterParams, UsuariLogat usuariLogat) { return (root, query, builder) -> { List<Predicate> predicates = buildListOfPredicates(root, builder, usuariFilterParams); query.select(root.get("id")).distinct(true); Subquery<Equip> subqueryEquip = buildSubqueryEquip(query, builder, usuariLogat.getId()); Join<Usuari, Equip> equip = root.join("equips", JoinType.LEFT); Join<Usuari, Rol> rol = root.join("rol"); predicates.add(builder.and( builder.notEqual(root.get("id"), usuariLogat.getId()), builder.or( builder.in(equip.get("id")).value(subqueryEquip), builder.equal(rol.get("id"), (RolsEnum.ROLE_USUARI_LABORATORI.getId())) ), builder.greaterThan(rol.get("id"), usuariLogat.getFirstRole().getId()))); query.where(builder.and(predicates.toArray(new Predicate[0]))); return query.getRestriction(); }; }
子查询构建函数
private static Subquery<Equip> buildSubqueryEquip(CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder, long usuariLogatId) { Subquery<Equip> subqueryEquip = query.subquery(Equip.class); Root<Equip> subEquip = subqueryEquip.from(Equip.class); Join<Equip, Usuari> subUsuariEquip = subEquip.join("usuaris"); return subqueryEquip.select(subEquip).where(criteriaBuilder.equal(subUsuariEquip.get("id"), usuariLogatId)); }
问题根源在于子查询返回的是Equip实体对象,但in语句中对比的是equip.get("id")(基础数据类型,如Long),类型不匹配导致Hibernate抛出类型转换错误。Spring 3.x配套的Hibernate 6.x对Criteria API的类型校验更为严格,而Spring 2.7使用的Hibernate 5.x允许宽松的类型匹配,这就是迁移后报错的原因。
修改步骤:
- 修改子查询,使其返回
Equip的ID而非整个实体:
private static Subquery<Long> buildSubqueryEquip(CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder, long usuariLogatId) { Subquery<Long> subqueryEquip = query.subquery(Long.class); Root<Equip> subEquip = subqueryEquip.from(Equip.class); Join<Equip, Usuari> subUsuariEquip = subEquip.join("usuaris"); // 选择Equip的id字段 return subqueryEquip.select(subEquip.get("id")).where(criteriaBuilder.equal(subUsuariEquip.get("id"), usuariLogatId)); }
- 调整Specification中的子查询类型声明:
// 将子查询类型改为Long,与返回值匹配 Subquery<Long> subqueryEquip = buildSubqueryEquip(query, builder, usuariLogat.getId());
这样修改后,in语句对比的是同类型的ID值,即可解决类型转换错误,让查询正常执行。
内容的提问来源于stack exchange,提问作者Albert Lazaro de Lara

