Hibernate pagination mechanism

I am using in query and in hibernate call back. both are working as expected. Hibernate Query executes for results in between First and Max size given. Here Seems like you passed SQL not HQL to query. if yes it shouldn't work.

-- See my code here.

        Query query = this.getSession().createQuery("FROM QueryType");
        query.setFirstResult(0);
        query.setMaxResults(20);
        List toDelete = query.list();

and in log:

select * from ( select -- ALL column names. (dont want to share here.) from MY_TBL_NAME querytype0_ ) where rownum <= ?


There are quite a few ways to paginate.

HQL and setFirstResult, setMaxResults API

Session session = sessionFactory.openSession();
Query query = session.createQuery("From Foo");
query.setFirstResult(0);
query.setMaxResults(10);
List<Foo> fooList = query.list();
//Total count
String countQ = "Select count (f.id) from Foo f";
Query countQuery = session.createQuery(countQ);
Long countResults = (Long) countQuery.uniqueResult();
//Last Page
int pageSize = 10;
int lastPageNumber = (int) ((countResult / pageSize) + 1);

HQL and the ScrollableResults API

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;
}
//Total count
resultScroll.last();
int totalResults = resultScroll.getRowNumber() + 1;

Simply the Criteria API

Criteria criteria = session.createCriteria(Foo.class);
criteria.setFirstResult(0);
criteria.setMaxResults(pageSize);
List<Foo> firstPage = criteria.list();
//Total count
Criteria criteriaCount = session.createCriteria(Foo.class);
criteriaCount.setProjection(Projections.rowCount());
Long count = (Long) criteriaCount.uniqueResult();

baeldung lists them all with examples.


You can use the JPA pagination for both entity queries and native SQL.

To limit the underlying query ResultSet size, the JPA Query interface provides the setMaxResults method.

Navigating the following page requires positioning the result set where the last page ended. For this purpose, the JPA Query interface provides the setFirstResult method.

JPQL

List<Post> posts = entityManager.createQuery("""
    select p
    from Post p
    order by p.createdOn
    """, Post.class)
.setFirstResult(10)
.setMaxResults(10)
.getResultList();

DTO projection queries

The JPA query pagination is not limited to entity queries that return entities only. You can use it for DTO projections as well.

List<PostCommentSummary> summaries = entityManager.createQuery("""
    select new
       com.vladmihalcea.book.hpjp.hibernate.fetching.PostCommentSummary(
           p.id, p.title, c.review
       )
    from PostComment c
    join c.post p
    order by c.createdOn
    """)
.setMaxResults(10)
.getResultList();

Native SQL queries

The JPA query pagination is not limited to entity queries, such as JPQL or Criteria API. You can use it for native SQL queries as well.

List<Post> posts = entityManager.createQuery("""
    select p
    from Post p
    left join fetch p.comments
    where p.title like :titlePattern
    order by p.createdOn
    """, Post.class)
.setParameter("titlePattern", "High-Performance Java Persistence %")
.setMaxResults(5)
.getResultList();

JOIN FETCH and pagination

However, if we try to use the JOIN FETCH clause in the entity query while also using JPA pagination:

List<Post> posts = entityManager.createQuery("""
    select p
    from Post p
    left join fetch p.comments
    where p.title like :titlePattern
    order by p.createdOn
    """, Post.class)
.setParameter("titlePattern", "High-Performance Java Persistence %")
.setMaxResults(5)
.getResultList();

Hibernate will issue the following warning message:

HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!

And the executed SQL query will lack the pagination clause:

SELECT p.id AS id1_0_0_,
       c.id AS id1_1_1_,
       p.created_on AS created_2_0_0_,
       p.title AS title3_0_0_,
       c.created_on AS created_2_1_1_,
       c.post_id AS post_id4_1_1_,
       c.review AS review3_1_1_,
       c.post_id AS post_id4_1_0__,
       c.id AS id1_1_0__
FROM post p
LEFT OUTER JOIN post_comment c ON p.id=c.post_id
WHERE p.title LIKE :titlePattern
ORDER BY p.created_on

This is because Hibernate wants to fetch entities fully along with their collections as indicated by the JOIN FETCH clause while the SQL-level pagination could truncate the ResultSet possibly leaving a parent Post entity with fewer elements in the comments collection.

The problem with the HHH000104 warning is that Hibernate will fetch the product of Post and PostComment entities, and due to the result set size, the query response time is going to be significant.

In order to work around this limitation, you have to use a Window Function query:

@NamedNativeQuery(
    name = "PostWithCommentByRank",
    query = """
        SELECT *
        FROM (
            SELECT
                *,
                DENSE_RANK() OVER (
                    ORDER BY "p.created_on", "p.id"
                ) rank
            FROM (
                SELECT
                    p.id AS "p.id", p.created_on AS "p.created_on",
                    p.title AS "p.title", pc.post_id AS "pc.post_id",
                    pc.id as "pc.id", pc.created_on AS "pc.created_on",
                    pc.review AS "pc.review"
                FROM  post p
                LEFT JOIN post_comment pc ON p.id = pc.post_id
                WHERE p.title LIKE :titlePattern
                ORDER BY p.created_on
            ) p_pc
        ) p_pc_r
        WHERE p_pc_r.rank <= :rank
        """,
    resultSetMapping = "PostWithCommentByRankMapping"
)
@SqlResultSetMapping(
    name = "PostWithCommentByRankMapping",
    entities = {
        @EntityResult(
            entityClass = Post.class,
            fields = {
                @FieldResult(name = "id", column = "p.id"),
                @FieldResult(name = "createdOn", column = "p.created_on"),
                @FieldResult(name = "title", column = "p.title"),
            }
        ),
        @EntityResult(
            entityClass = PostComment.class,
            fields = {
                @FieldResult(name = "id", column = "pc.id"),
                @FieldResult(name = "createdOn", column = "pc.created_on"),
                @FieldResult(name = "review", column = "pc.review"),
                @FieldResult(name = "post", column = "pc.post_id"),
            }
        )
    }
)

For more details about using Window Functions to fix the HHH000104 issue as well as the code for DistinctPostResultTransformer.