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

от автора

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

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

Например, у нас есть две сущности: 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

👨‍💻 Джуниор


ссылка на оригинал статьи https://habr.com/ru/articles/856092/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *