如何在Spring Data JPA中同时实现父实体与子实体的分页查询
Spring Data JPA 实现分页父记录并限制关联子记录数量方案
前置准备:实体类定义
首先定义父子实体,关联关系设置为懒加载避免不必要的关联查询:
// 父实体 @Entity public class Parent { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 其他业务字段 @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY) private List<Child> children; // 省略getter、setter } // 子实体 @Entity public class Child { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_id") private Parent parent; // 子记录排序字段示例,如创建时间 private LocalDateTime createTime; // 省略getter、setter }
核心实现步骤
1. 分页查询当前页的父记录
首先调用Spring Data JPA的分页查询能力,获取单页10条父记录,同时提取当前页所有父记录的ID:
// ParentRepository接口定义 public interface ParentRepository extends JpaRepository<Parent, Long> { } // 业务层调用 int pageNum = 0; // 页码从0开始,按需调整 Pageable parentPageable = PageRequest.of(pageNum, 10, Sort.by("id").ascending()); Page<Parent> parentPage = parentRepository.findAll(parentPageable); List<Parent> currentPageParents = parentPage.getContent(); // 提取当前页父ID列表 List<Long> parentIds = currentPageParents.stream() .map(Parent::getId) .collect(Collectors.toList());
2. 批量查询每个父记录对应的前10条子记录
推荐使用支持窗口函数的SQL实现单次查询拿到所有符合要求的子记录,避免N+1查询问题:
// ChildRepository接口定义 public interface ChildRepository extends JpaRepository<Child, Long> { // MySQL 8+ 原生查询,按parent_id分组取每组前10条子记录 @Query(value = "SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY parent_id ORDER BY create_time DESC) AS rn FROM child WHERE parent_id IN :parentIds) t WHERE t.rn <= 10", nativeQuery = true) List<Child> findTop10PerParent(@Param("parentIds") List<Long> parentIds); } // 业务层调用查询子记录 List<Child> children = childRepository.findTop10PerParent(parentIds);
如果使用的数据库不支持窗口函数,也可以循环每个父ID单独查询前10条子记录,数据量不大的场景下性能也可接受。
3. 子记录关联到对应父实体
将查询到的子记录按父ID分组,设置到对应父实体的children属性中:
// 按父ID分组子记录 Map<Long, List<Child>> childGroupMap = children.stream() .collect(Collectors.groupingBy(child -> child.getParent().getId())); // 给每个父实体填充对应子记录 currentPageParents.forEach(parent -> parent.setChildren(childGroupMap.getOrDefault(parent.getId(), Collections.emptyList())) );
最终返回的parentPage对象就符合要求:每页10条父记录,每条父记录携带最多10条关联子记录。
注意事项
- 不要直接在
@OneToMany注解上配置结果限制,该限制会作用于整个查询的总结果集,而非每个父记录对应的子记录分组 - 避免遍历父列表逐个查询子记录,单页10条父记录的场景下会产生11次SQL查询(1次查父+10次查子),批量查询仅需2次SQL,性能提升明显
内容的提问来源于stack exchange,提问作者Prabhakar
相关产品推荐
相关产品推荐

