Hibernate pagination mechanism

后端 未结 3 1602
礼貌的吻别
礼貌的吻别 2020-12-14 11:26

I am trying to use Hibernate pagination for my query (PostgreSQL )

I set setFirstResult(0), setMaxResults(20) for my SQL query. My code is

3条回答
  •  情书的邮戳
    2020-12-14 12:20

    As I explained in this article, 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 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 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 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 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, check out this article.

提交回复
热议问题