如何在JPA Query中通过GROUP BY生成公司ID对应设备名列表的Map结构
结论
标准JPQL本身没有原生支持直接返回聚合后嵌套列表结构的语法,但是根据你使用的JPA实现和版本,可以通过以下方案实现需求:
方案1:Hibernate 6+ 版本实现(最贴近你期望的写法)
Hibernate 6及以上版本内置了collect_list/collect_set聚合函数,可以直接将多行值聚合为列表,你可以直接返回你定义的CompanyDevices对象:
public interface SomeRepository extends JpaRepository<SomeEntity, Long> { @Query("SELECT " + "new 你实际的包路径.CompanyDevices(C.id, collect_list(D.deviceName)) " + "FROM Company C " + "JOIN C.employees E " + "JOIN E.devices D " + "WHERE C.id IN :companyIds " + "GROUP BY C.id" ) List<CompanyDevices> findDevicesForCompanies(@Param("companyIds") List<Long> companyIds); }
如果需要直接返回Map<Long, List<String>>结构,搭配@MapKey注解即可实现:
@Query("SELECT " + "C.id as key, collect_list(D.deviceName) as value " + "FROM Company C " + "JOIN C.employees E " + "JOIN E.devices D " + "WHERE C.id IN :companyIds " + "GROUP BY C.id" ) @MapKey(name = "key") Map<Long, List<String>> findDevicesForCompanies(@Param("companyIds") List<Long> companyIds);
方案2:低版本JPA实现使用原生SQL
如果你使用的JPA版本不支持collect_list函数,可以使用对应数据库的字符串聚合函数,先把设备名拼接为单个字符串,后续再拆分转换为列表,以下是MySQL为例的写法:
@Query(value = "SELECT c.id, GROUP_CONCAT(d.device_name SEPARATOR ',') as device_names " + "FROM company c " + "JOIN employee e ON c.id = e.company_id " + "JOIN devices d ON e.id = d.employee_id " + "WHERE c.id IN :companyIds " + "GROUP BY c.id", nativeQuery = true) List<Object[]> findRawDeviceGroups(@Param("companyIds") List<Long> companyIds);
不同数据库的字符串聚合函数有差异:PostgreSQL用string_agg、Oracle用listagg,需要根据你使用的数据库调整语法
方案3:保留现有写法+Stream分组(通用性最强,性能开销可忽略)
你现有的写法已经是兼容性最好的实现,查询结果只需要一行Stream代码即可完成分组,适配所有JPA实现和数据库,额外性能开销极低:
// 调用现有Repository方法后直接分组 Map<Long, List<String>> companyDeviceMap = findDevicesForCompanies(companyIds).stream() .collect(Collectors.groupingBy( CompanyDevice::getCompanyId, Collectors.mapping(CompanyDevice::getDeviceName, Collectors.toList()) ));
该方式的分组操作只是对已加载到内存的对象做引用重组,不会产生额外的大量对象开销,性能几乎和直接返回聚合结构一致。
内容的提问来源于stack exchange,提问作者Lorthiz
相关产品推荐
相关产品推荐

