2. HQL 分页实现:setFirstResult 与 setMaxResults

Hibernate 中最简单直接的分页方式是使用 HQL 查询结合 setFirstResultsetMaxResults 方法:

Session session = sessionFactory.openSession();
Query<Foo> query = session.createQuery("From Foo", Foo.class);
query.setFirstResult(0);
query.setMaxResults(10);
List<Foo> fooList = query.list();

✅ 这种方式与 JPA 的 JQL 实现几乎一致,仅查询语言不同
⚠️ 启用 Hibernate 日志后,实际执行的 SQL 会包含 limit 子句:

Hibernate: 
    select
        foo0_.id as id1_1_,
        foo0_.name as name2_1_ 
    from
        Foo foo0_ limit ?

2.1. 总数统计与末页计算

完整的分页方案必须包含总记录数统计

String countQ = "Select count (f.id) from Foo f";
Query<Long> countQuery = session.createQuery(countQ, Long.class);
Long countResults = countQuery.uniqueResult();

根据总数和页面大小计算末页页码

int pageSize = 10;
int lastPageNumber = (int) (Math.ceil(countResults / pageSize));

下面是完整的分页测试示例,包含末页获取逻辑:

@Test
public void givenEntitiesExist_whenRetrievingLastPage_thenCorrectSize() {
    int pageSize = 10;
    String countQ = "Select count (f.id) from Foo f";
    Query<Long> countQuery = session.createQuery(countQ, Long.class);
    Long countResults = (Long) countQuery.uniqueResult();
    int lastPageNumber = (int) (Math.ceil(countResults / pageSize));

    Query<Foo> selectQuery = session.createQuery("From Foo", Foo.class);
    selectQuery.setFirstResult((lastPageNumber - 1) * pageSize);
    selectQuery.setMaxResults(pageSize);
    List<Foo> lastPage = selectQuery.list();

    assertThat(lastPage, hasSize(lessThan(pageSize + 1)));
}

3. HQL 结合 ScrollableResults 实现分页

使用 ScrollableResults 可以减少数据库调用次数,通过流式处理结果集避免重复查询:

String hql = "FROM Foo f order by f.name";
Query query = session.createQuery(hql);
int pageSize = 10;

ScrollableResults resultScroll = query.scroll(ScrollMode.FORWARD_ONLY);
resultScroll.first();
resultScroll.scroll(0);
List<Foo> fooPage = Lists.newArrayList();
int i = 0;
while (pageSize > i++) {
    fooPage.add((Foo) resultScroll.get(0));
    if (!resultScroll.next())
        break;
}

✅ 优势:

  • 单次数据库调用
  • 可直接获取结果集总数(无需额外查询)
resultScroll.last();
int totalResults = resultScroll.getRowNumber() + 1;

⚠️ 注意:虽然滚动查询高效,但处理大数据量窗口时可能占用较多内存

4. Criteria API 实现分页

Criteria API 提供更灵活的分页方案:

CriteriaQuery<Foo> selectQuery = session.getCriteriaBuilder().createQuery(Foo.class);
selectQuery.from(Foo.class);
SelectionQuery<Foo> query = session.createQuery(selectQuery);
query.setFirstResult(0);
query.setMaxResults(pageSize);
List<Foo> firstPage = query.list();

获取总数同样简单:

HibernateCriteriaBuilder qb = session.getCriteriaBuilder();
CriteriaQuery<Long> cq = qb.createQuery(Long.class);
cq.select(qb.count(cq.from(Foo.class)));
final Long count = session.createQuery(cq).getSingleResult();

✅ 特点:

  • 类型安全(编译时检查)
  • 代码比纯 HQL 略冗余但更灵活
  • 支持动态条件构建

5. 总结

本文介绍了 Hibernate 中三种主流分页实现方案:

  1. HQL + setFirstResult/setMaxResults

    • 最简单直接的方式
    • 需要额外查询获取总数
  2. ScrollableResults API

    • 单次数据库调用
    • 流式处理适合大数据量
    • 注意内存占用问题
  3. Criteria API

    • 类型安全且灵活
    • 适合动态查询场景
    • 代码稍复杂但功能强大

项目完整实现可在 GitHub 项目 获取,基于 Eclipse 构建,可直接导入运行。


原始标题:Hibernate Pagination

« 上一篇: JPA 分页实现指南
» 下一篇: Baeldung周报14