Это предупреждение, которое выведет хибернейт, если для осуществления пагинации ему придется загрузить ВСЕ данные из таблицы, а не по одной странице.

? Почему возникает?

Например, у нас есть две сущности: Post и Comment. Каждый пост может иметь множество комментариев:

@Entity
public class Post {

    @Id
    private Long id;

    private String title;

    @OneToMany
    private List<Comment> comments;
}
@Entity
public class Comment {

    @Id
    private Long id;

    private String content;
}

Посты мы достаем с помощью репозитория (делаем join fetch, чтобы подтянуть всё одним запросом):

public interface PostRepository extends JpaRepository<Post, Long> {
    
    @Query("SELECT p FROM Post p JOIN FETCH Comment c)
    List<Post> findAll(Pageable pageable);
}

С точки зрения базы данные будут выглядеть примерно так:

p.id

p.title

c.id

c.content

1

пост1

1

коммент1

1

пост1

2

коммент2

2

пост2

3

коммент3

То есть, количество записей не будет совпадать с количество постов (на каждый пост будет приходиться столько записей, сколько у этого поста комментариев), из-за этого применить пагинацию на уровне sql не получится.

Хибернейт это понимает и ему приходится делать пагинацию на уровне кода, а для этого надо выгрузить все данные.

?‍? Как пофиксить?

1. Использовать два отдельных запросаПервый запрос вытащит id нужных нам постов и именно к этому запросу будет применена пагинация.Второй вытащит уже сами сущности, к этому запросу пагинацию не применяем.

2. Использовать подзапросЛогика такая же, как в первом пункте, но айдишники мы найдем подзапросом, а не отдельно:

@Query("""
    select p
    from Post p
    left join fetch p.comments
    where p.id in (
        select id
        from (
          select id,
          from Post
          order by id
          offset :offset
          fetch first :limit rows only
        )
    )
   order by p.id
    """
)
List<Post> findAll(int offset, int limit);

3. Использовать Blaze. Blaze предоставляет более удобное апи для построение запросов через код:

public List<Post> findAll() {
    return criteriaBuilderFactory
        .create(entityManager, Post.class)
         .fetch("comments")
         .orderBy("id", true)
         .page(
             (int) pageRequest.getOffset(),
             pageRequest.getPageSize()
         )
         .withCountQuery(false)
         .getResultList();
}

Блейз достаточно умный и поймет, что в этом случае надо сделать подзапрос:

SELECT
    p1_0.id,
    p1_0.title
    c1_0.id,
    c1_0.post_id,
    c1_0.content
FROM
    post p1_0
LEFT JOIN
    post_comment c1_0 ON p1_0.id=c1_0.post_id
WHERE p1_0.id in (
    SELECT
        p2_0.id
    FROM
        post p2_0
    ORDER BY
        p2_0.id ASC
    FETCH FIRST 25 ROWS ONLY
)
ORDER BY
    p1_0.id ASC

Также, можно попросить хибернейт кидать исключение при in-memory пагинации:

spring.jpa.properties.hibernate.query.fail_on_pagination_over_collection_fetch=true

?‍? Джуниор

Комментарии (0)