如何在JPA Criteria查询中实现分组并拼接字段值?
解决JPA Criteria实现分组拼接字段的问题
看起来你想要通过JPA Criteria实现按incomeChannelCode和logicalUnitCode分组,将对应的logicalUnitIdent用逗号拼接的效果,之前尝试group_concat没生效大概率是因为缺少分组配置或者数据库方言的问题,下面给你两种可行的方案:
方案一:使用数据库聚合函数(以MySQL为例)
MySQL的GROUP_CONCAT函数确实可以实现字段拼接,但需要注意两个关键点:
- 必须对分组字段添加
GROUP BY - 确保Hibernate配置了正确的MySQL方言(比如
org.hibernate.dialect.MySQL8Dialect)
修改你的Criteria查询代码如下:
public List<IncomeChannelCategoryMap> allIncomeChannels(final List<String> list) { final CriteriaQuery<IncomeChannelCategoryMap> criteriaQuery = builder.createQuery(IncomeChannelCategoryMap.class); final Root<IncomeChannelMapEntity> root = criteriaQuery.from(IncomeChannelMapEntity.class); // 定义要分组的字段 Expression<String> incomeChannelCode = root.get(IncomeChannelMapEntity_.incomeChannel).get(IncomeChannelEntity_.code); Expression<String> logicalUnitCode = root.get(IncomeChannelMapEntity_.logicalUnitCode); // 使用GROUP_CONCAT拼接logicalUnitIdent Expression<String> concatIdent = builder.function( "group_concat", String.class, root.get(IncomeChannelMapEntity_.logicalUnitIdent) ); // 构造查询选择项 criteriaQuery.multiselect( incomeChannelCode, logicalUnitCode, concatIdent, root.get(IncomeChannelMapEntity_.keyword) // 注意:如果keyword不是分组字段,可能需要用聚合函数处理,比如取第一个 ); // 添加WHERE条件 Predicate codePredicate = incomeChannelCode.in(list); criteriaQuery.where(codePredicate); // 添加GROUP BY criteriaQuery.groupBy(incomeChannelCode, logicalUnitCode); return entityManager.createQuery(criteriaQuery).getResultList(); }
注意:如果你的
keyword字段在同一分组下有不同值,需要用聚合函数(比如builder.max()、builder.first())处理,否则数据库会报错。如果keyword和分组字段是一一对应的,直接加入group by即可。
如果是其他数据库,替换对应的聚合函数:
- PostgreSQL:使用
string_agg(root.get(...), ','),对应函数名"string_agg" - Oracle:使用
listagg(root.get(...), ',') within group (order by root.get(...)),需要调整function的参数
方案二:内存中分组拼接(兼容性更好)
如果不想依赖数据库特定函数,或者数据库不支持类似的聚合函数,可以先查询出所有原始数据,再用Java代码在内存中分组拼接:
public List<IncomeChannelCategoryMap> allIncomeChannels(final List<String> list) { // 先查询所有符合条件的原始数据 final CriteriaQuery<IncomeChannelCategoryMap> criteriaQuery = builder.createQuery(IncomeChannelCategoryMap.class); final Root<IncomeChannelMapEntity> root = criteriaQuery.from(IncomeChannelMapEntity.class); final List<Selection<?>> selections = new ArrayList<>(); selections.add(root.get(IncomeChannelMapEntity_.incomeChannel).get(IncomeChannelEntity_.code)); selections.add(root.get(IncomeChannelMapEntity_.logicalUnitCode)); selections.add(root.get(IncomeChannelMapEntity_.logicalUnitIdent)); selections.add(root.get(IncomeChannelMapEntity_.keyword)); criteriaQuery.multiselect(selections); Predicate codePredicate = root.get(IncomeChannelMapEntity_.incomeChannel).get(IncomeChannelEntity_.code).in(list); criteriaQuery.where(codePredicate); List<IncomeChannelCategoryMap> rawList = entityManager.createQuery(criteriaQuery).getResultList(); // 内存中分组拼接 return rawList.stream() .collect(Collectors.groupingBy( item -> Arrays.asList(item.getIncomeChannelCode(), item.getLogicalUnitCode()), Collectors.collectingAndThen( Collectors.toList(), group -> { IncomeChannelCategoryMap merged = new IncomeChannelCategoryMap(); merged.setIncomeChannelCode(group.get(0).getIncomeChannelCode()); merged.setLogicalUnitCode(group.get(0).getLogicalUnitCode()); merged.setKeyword(group.get(0).getKeyword()); // 同样,这里如果keyword有不同值需要处理 merged.setLogicalUnitIdent(group.stream() .map(IncomeChannelCategoryMap::getLogicalUnitIdent) .collect(Collectors.joining(","))); return merged; } ) )) .values() .stream() .collect(Collectors.toList()); }
这种方案的优点是不依赖数据库特性,兼容性强,缺点是如果数据量很大,会占用较多内存,适合数据量不大的场景。
为什么之前的GROUP_CONCAT没生效?
大概率是因为你没有添加GROUP BY语句,数据库不知道按照哪些字段分组拼接,所以返回的还是原始的多行数据。加上分组后,聚合函数才能正确工作。
内容的提问来源于stack exchange,提问作者None
相关产品推荐
相关产品推荐

