JPA中能否直接使用Map<K,V>作为查询的返回投影类型?
JPA分组查询直接返回单个Map的问题解答
结论
默认情况下不能直接使用单个Map<String, Integer>作为该分组查询的返回类型,符合JPA规范的实现(包括Spring Data JPA)没有自动将多行分组结果聚合为[scholarId: 计数]结构Map的默认逻辑。
原因说明
你写的分组查询会返回多行结果,每行包含两个值:scholarId和对应jobDescription.id的计数:
- 当返回类型声明为
List<Map>时,Spring Data JPA会将每一行单独封装为一个Map,Map的键为查询字段名/别名,值为对应字段的取值,因此可以正常拿到数据。 - 当返回类型声明为单个
Map时,JPA会默认认为该查询最多返回1行结果,会尝试调用getSingleResult()获取单行数据,再把这一行的字段作为Map的键、字段值作为Map的值。要么匹配不到对应返回结构返回空,要么多结果时直接抛出NonUniqueResultException,不可能自动把多行数据聚合为你预期的scholarId为键、计数为值的Map结构。
可行实现方案
方案1:手动转换(最通用,无额外依赖)
先声明方法返回List<Object[]>获取原始结果,再用Stream手动聚合为目标Map:
// 聚合为目标Map的默认实现 default Map<String, Integer> countScholarJobApplicationsInJobStatusSelected() { List<Object[]> rawResults = getRawSelectedApplicationCount(); return rawResults.stream() .collect(Collectors.toMap( row -> (String) row[0], row -> ((Number) row[1]).intValue() )); } // 原始查询方法 @Query("select sja.scholar.id, count(sja.jobDescription.id) from ScholarJobApplication sja " + "where sja.jobStatus='SELECTED' " + "group by sja.scholar.id") List<Object[]> getRawSelectedApplicationCount();
方案2:使用@MapKey注解(Spring Data JPA专属)
如果不想手动转换,也可以先定义投影接口接收每行结果,再用@MapKey指定用scholarId作为聚合Map的键:
// 投影接口 interface ScholarApplicationCountProjection { String getScholarId(); Integer getCount(); } // 查询方法 @Query("select sja.scholar.id as scholarId, count(sja.jobDescription.id) as count from ScholarJobApplication sja " + "where sja.jobStatus='SELECTED' " + "group by sja.scholar.id") @MapKey("scholarId") Map<String, ScholarApplicationCountProjection> countScholarSelectedApplications();
如果只需要计数,可以直接取Map中每个value的getCount()即可,也可以再做一层简单封装得到Map<String, Integer>结构。
内容的提问来源于stack exchange,提问作者Sugata Kar
相关产品推荐
相关产品推荐

