You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 18:27:03