2. HQL 分页实现:setFirstResult 与 setMaxResults
Hibernate 中最简单直接的分页方式是使用 HQL 查询结合 setFirstResult
和 setMaxResults
方法:
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 中三种主流分页实现方案:
HQL + setFirstResult/setMaxResults
- 最简单直接的方式
- 需要额外查询获取总数
ScrollableResults API
- 单次数据库调用
- 流式处理适合大数据量
- 注意内存占用问题
Criteria API
- 类型安全且灵活
- 适合动态查询场景
- 代码稍复杂但功能强大
项目完整实现可在 GitHub 项目 获取,基于 Eclipse 构建,可直接导入运行。