如何修改JPA代码按JsonBMap指定字段排序?解决DISTINCT报错
问题原因
PostgreSQL对SELECT DISTINCT有严格约束:当使用DISTINCT去重时,ORDER BY中用到的所有字段/表达式必须出现在SELECT的返回列表中。你的代码里,排序逻辑使用了jsonb_extract_path_text生成的表达式,但这个表达式并未加入multiselect的选择列表,因此触发了报错。
解决方案
以下是三种可行的解决方式,可根据业务场景选择:
方案1:将排序表达式加入SELECT列表
修改排序逻辑,收集所有用于排序的表达式,然后将它们添加到multiselect的选择项中,后续处理结果时忽略这些额外字段即可。
修改setOrderBy方法,收集排序表达式
private static List<Expression<?>> setOrderBy(Collection<String> orderBys, CriteriaBuilder criteriaBuilder, CriteriaQuery criteriaQuery, From root, Map<String, Map<String, String>> jsonBMap) { final List<Order> orders = new ArrayList<>(); final List<Expression<?>> orderExpressions = new ArrayList<>(); // 新增:收集排序用的表达式 criteriaQuery.distinct(true); var mapName = jsonBMap.keySet().stream().findFirst().orElse(null); var map = jsonBMap.get(mapName); orderBys.stream() .map(field -> { var isFieldDesc = field.startsWith("-"); var fieldWithoutSign = isFieldDesc ? field.substring(1) : field; Expression<?> sortExpr; if (map.get(fieldWithoutSign) != null) { var value = map.get(fieldWithoutSign); Expression<?>[] expressions = Stream.of(List.of(root.get(mapName)), getExpressions(value, criteriaBuilder)) .flatMap(List::stream) .toArray(Expression<?>[]::new); sortExpr = criteriaBuilder.function("jsonb_extract_path_text", String.class, expressions); orderExpressions.add(sortExpr); // 收集JsonB排序表达式 } else { String[] explodedPath = fieldWithoutSign.split("\\."); Path<?> selection = explodedPathAsPath(root, explodedPath); sortExpr = selection; orderExpressions.add(sortExpr); // 收集普通字段排序表达式 } return isFieldDesc ? criteriaBuilder.desc(sortExpr) : criteriaBuilder.asc(sortExpr); }).forEach(orders::add); criteriaQuery.orderBy(orders); return orderExpressions; // 返回排序表达式列表 }
调用multiselect时加入排序表达式
// 调用排序方法,获取排序表达式列表 List<Expression<?>> orderExpressions = setOrderBy(orderBys, criteriaBuilder, criteriaQuery, root, jsonBMap); // 构造选择列表:主对象 + 所有排序表达式 List<Selection<?>> selectItems = new ArrayList<>(); selectItems.add(mainObject); selectItems.addAll(orderExpressions); criteriaQuery.multiselect(selectItems); TypedQuery<Object[]> query = entityManager.createQuery(criteriaQuery); // 处理结果流,只保留主对象,忽略排序字段 return query.getResultStream().map(row -> (YourEntityType) row[0]);
方案2:移除DISTINCT(业务允许时)
如果你的业务场景不需要去重,直接删除criteriaQuery.distinct(true);这行代码即可。此时PostgreSQL不会再强制要求ORDER BY表达式出现在SELECT列表中。
方案3:子查询排序 + 外层DISTINCT
适合必须保留DISTINCT且不想返回额外字段的场景:先在子查询中完成排序,再在外层查询中对结果去重。
// 1. 子查询:仅做排序,不做DISTINCT CriteriaQuery<YourEntityType> subQuery = criteriaBuilder.createQuery(YourEntityType.class); From<YourEntityType, YourEntityType> subRoot = subQuery.from(YourEntityType.class); // 子查询中添加WHERE条件(如果有) subQuery.select(subRoot); setOrderBy(orderBys, criteriaBuilder, subQuery, subRoot, jsonBMap); // 子查询执行排序,注意不要加DISTINCT // 2. 外层查询:基于子查询的ID做DISTINCT CriteriaQuery<YourEntityType> mainQuery = criteriaBuilder.createQuery(YourEntityType.class); Root<YourEntityType> mainRoot = mainQuery.from(YourEntityType.class); mainQuery.select(mainRoot).distinct(true) .where(criteriaBuilder.in(mainRoot.get("id")).value(subQuery)); // 通过ID关联子查询结果 TypedQuery<YourEntityType> query = entityManager.createQuery(mainQuery); return query.getResultStream();
内容的提问来源于stack exchange,提问作者Pawan.Java
相关产品推荐
相关产品推荐

