如何用JPA Criteria连接无关联分页表并合并结果集
可行性与实现方案
完全可行,核心是利用两表共有的groupId字段关联或合并数据,同时保留分页能力,以下是几种实用实现方式:
一、定义DTO统一结果结构
由于两个表实体结构不同,先创建一个DTO来封装合并后的数据:
public class CombinedInquiryDTO { // HistoryTable相关字段 private Long historyId; private String historyContent; // DataTable相关字段 private Long dataId; private String dataContent; // 共同关联字段 private Long groupId; // 生成getter、setter }
子方案1:先分页获取groupId,再组装数据
- 先根据过滤条件查询符合要求的唯一groupId集合并分页:
public Page<Long> findGroupIdsByCondition(InquiryRequest req, int pageNumber, int pageSize) { Pageable pageable = PageRequest.of(pageNumber, pageSize); return historyRepository.findAll((root, query, cb) -> { List<Predicate> predicates = new ArrayList<>(); filterByCarId(predicates, cb, root, req.getCarId()); filterByCarnameId(predicates, cb, root, req.getCarnameId()); // 只查询去重后的groupId query.select(root.get("groupId")).distinct(true); return cb.and(predicates.toArray(new Predicate[0])); }, pageable); }
- 根据分页后的groupId批量查询两表数据,组装成DTO:
public Page<CombinedInquiryDTO> getCombinedInquiry(InquiryRequest req, int pageNumber, int pageSize) { Page<Long> groupIdPage = findGroupIdsByCondition(req, pageNumber, pageSize); List<Long> groupIds = groupIdPage.getContent(); if (groupIds.isEmpty()) { return new PageImpl<>(Collections.emptyList(), groupIdPage.getPageable(), 0); } // 按groupId分组存储两表数据 Map<Long, HistoryTable> historyMap = historyRepository.findByGroupIdIn(groupIds).stream() .collect(Collectors.toMap(HistoryTable::getGroupId, h -> h)); Map<Long, DataTable> dataMap = dataRepository.findByGroupIdIn(groupIds).stream() .collect(Collectors.toMap(DataTable::getGroupId, d -> d)); // 组装DTO列表 List<CombinedInquiryDTO> dtoList = groupIds.stream().map(groupId -> { CombinedInquiryDTO dto = new CombinedInquiryDTO(); HistoryTable history = historyMap.get(groupId); if (history != null) { dto.setHistoryId(history.getId()); dto.setHistoryContent(history.getContent()); } DataTable data = dataMap.get(groupId); if (data != null) { dto.setDataId(data.getId()); dto.setDataContent(data.getContent()); } dto.setGroupId(groupId); return dto; }).collect(Collectors.toList()); return new PageImpl<>(dtoList, groupIdPage.getPageable(), groupIdPage.getTotalElements()); }
子方案2:用JPQL/原生SQL联表查询
直接通过联表查询合并结果,映射到DTO:
@Query("SELECT new com.yourpackage.CombinedInquiryDTO(" + "h.id, h.content, d.id, d.content, coalesce(h.groupId, d.groupId)) " + "FROM HistoryTable h " + "LEFT JOIN DataTable d ON h.groupId = d.groupId " + "WHERE h.carId = :carId AND h.carnameId = :carnameId " + "UNION " + "SELECT new com.yourpackage.CombinedInquiryDTO(" + "h.id, h.content, d.id, d.content, coalesce(h.groupId, d.groupId)) " + "FROM DataTable d " + "LEFT JOIN HistoryTable h ON d.groupId = h.groupId " + "WHERE d.carId = :carId AND d.carnameId = :carnameId") Page<CombinedInquiryDTO> findCombinedInquiry( @Param("carId") String carId, @Param("carnameId") String carnameId, Pageable pageable);
注:使用
UNION需保证两次查询的字段数量、类型完全匹配;如果用原生SQL,需添加nativeQuery = true,并手动处理字段映射。
二、临时添加实体关联(无需修改数据库)
如果不想用DTO,可以在实体类中添加非数据库外键的关联关系,比如在HistoryTable中:
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "groupId", referencedColumnName = "groupId", insertable = false, updatable = false) private DataTable dataTable;
之后查询时可直接关联获取DataTable数据,但这种方式仅适合以单表为主的场景,且需确保groupId在两表中是唯一关联的,否则会出现数据重复。
注意事项
- 若
groupId在单表中存在重复,必须先做去重处理,避免结果集重复。 - 分页逻辑要基于唯一groupId集合,否则会导致分页数据不准确。
- 数据量大时优先选择「先分页groupId再批量查询」的方案,避免联表查询的性能损耗。
内容的提问来源于stack exchange,提问作者007
相关产品推荐
相关产品推荐

