Pagination
A blog with thousands of posts cannot show them all on one page. Pagination loads one portion at a time: posts 1–10, then 11–20 and so on. In Hibernate this is two methods of a query; the database itself returns only the required rows, so memory is not wasted.
A page of posts
The example creates 25 posts and shows the third page with ten posts per page:
// src/main/java/com/example/blog/Main.java
package com.example.blog;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
import java.util.List;
public class Main {
static final int PAGE_SIZE = 10;
public static void main(String[] args) {
try (EntityManagerFactory emf = Persistence.createEntityManagerFactory("blog")) {
emf.runInTransaction(em -> {
Author anna = new Author("Anna");
em.persist(anna);
for (int i = 1; i <= 25; i++) {
em.persist(new Post("Post " + i, anna));
}
});
emf.runInTransaction(em -> {
long total = em.createQuery("select count(p) from Post p", Long.class).getSingleResult();
long pages = (total + PAGE_SIZE - 1) / PAGE_SIZE;
IO.println("Posts: " + total + ", pages: " + pages);
int page = 3;
List<Post> posts = em.createQuery("select p from Post p order by p.id", Post.class)
.setFirstResult((page - 1) * PAGE_SIZE)
.setMaxResults(PAGE_SIZE)
.getResultList();
IO.println("Page " + page + ": " + posts.stream().map(Post::getTitle).toList());
});
}
}
}Hibernate: select count(p1_0.id) from Post p1_0
Posts: 25, pages: 3
Hibernate: select p1_0.id,p1_0.author_id,p1_0.title,p1_0.views from Post p1_0 order by p1_0.id offset ? rows fetch first ? rows only
Page 3: [Post 21, Post 22, Post 23, Post 24, Post 25]| Method | Meaning |
|---|---|
setFirstResult(n) | Skip the first n rows; counting starts at 0 |
setMaxResults(n) | Return at most n rows |
Page numbers are your own convention: for page 3 with ten rows per page you skip (3 - 1) * 10 = 20 rows. Hibernate turns the two methods into the syntax of your database: offset ... fetch first ... rows only in H2 and PostgreSQL, limit ... offset ... in MySQL. The total number of pages needs a separate count query.
Pagination with join fetch
Combining a page with join fetch of a collection is tricky: the joined rows repeat each post once per comment, so "first 2 rows" is not "first 2 posts". Hibernate 7 handles this itself with a subquery:
// src/main/java/com/example/blog/FetchPage.java
List<Post> posts = em.createQuery("select p from Post p join fetch p.comments order by p.id", Post.class)
.setFirstResult(0)
.setMaxResults(2)
.getResultList();Hibernate: select ... from (select p1_0.id,p1_0.author_id,p1_0.title,p1_0.views from Post p1_0 where exists(select 1 from Comment c1_0 where p1_0.id=c1_0.post_id) order by p1_0.id offset ? rows fetch first ? rows only) p1_0(id,author_id,title,views) join Comment c1_0 on p1_0.id=c1_0.post_id order by p1_0.id
[Hello, Hibernate, Records in Java 25]The inner query takes two posts, the outer one adds their comments. Older Hibernate versions instead loaded all rows and cut the page in memory.
Deep pages
offset makes the database read and discard all skipped rows, so page 5000 is slow. For endless feeds use a condition on the last seen key instead: where p.id > :lastId order by p.id with setMaxResults. This is called keyset pagination.
Common mistake
A page query without order by returns rows in whatever order the database likes. The order can change between requests, and the same post appears on two pages while another one never shows up. There is no error message — the bug is only visible to users. Always sort, and by a unique column: if you sort by date, add the id as a second key.