Hibernate pagination mechanism

后端 未结 3 1589
礼貌的吻别
礼貌的吻别 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<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, check out this article.

    0 讨论(0)
  • 2020-12-14 12:25

    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 <= ?

    0 讨论(0)
  • 2020-12-14 12:25

    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.

    0 讨论(0)
提交回复
热议问题