Автор оригинала: Vlad Mihalcea.
Вступление
Недавно я заметил спящий режим.query.fail_on_pagination_over_collection_fetch
свойство конфигурации, которое было введено в Hibernate 5.2, и я совершенно не представлял, что его можно использовать для предотвращения проблем с HHH000104 в режиме гибернации.
Как объяснялось ранее, если вы хотите преодолеть проблему “HHH000104: firstResult/maxResults, заданную при извлечении коллекции; применение в памяти!”, вам необходимо либо использовать 2 запроса, либо оконную функцию для извлечения и ограничения количества родительских записей, при этом всегда извлекая все связанные с ними дочерние сущности.
Даже если проблема HHH000104
регистрируется как предупреждающее сообщение, она может остаться незамеченной, когда объем данных довольно мал, и стать серьезной проблемой производительности только тогда, когда данные начнут накапливаться.
В этой статье я собираюсь объяснить, почему вы всегда должны активировать режим гибернации.свойство query.fail_on_pagination_over_collection_fetch
конфигурация и вместо предупреждающего сообщения вы получите исключение, которое вряд ли останется незамеченным.
Получение коллекции сущностей с разбиением на страницы
Учитывая, что у нас есть следующие Сообщение
и Комментарий к сообщению
сущности, которые образуют двунаправленную ассоциацию “один ко многим”:
Мы хотим получить 5 Post
сущностей вместе со всеми связанными с ними PostComment
дочерними сущностями при фильтрации заголовка
свойства родительских Post
сущностей, поэтому мы пишем следующий запрос JPQL:
Listposts = 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(); assertEquals(5, posts.size()); assertArrayEquals( LongStream.rangeClosed(1, 5).toArray(), posts.stream().mapToLong(Post::getId).toArray() );
Если мы запустим запрос JPQL выше, вы увидите, что будут возвращены только первые 5 Post
сущностей.
Однако, если вы посмотрите на журнал, вы увидите следующий вывод:
WARN [main]: o.h.h.i.a.QueryTranslatorImpl - HHH000104: firstResult/maxResults specified with collection fetch; applying in memory! DEBUG [main]: n.t.d.l.SLF4JQueryLoggingListener - Query:[" SELECT p.id AS id1_0_0_, pc.id AS id1_1_1_, p.created_on AS created_2_0_0_, p.title AS title3_0_0_, pc.created_on AS created_2_1_1_, pc.post_id AS post_id4_1_1_, pc.review AS review3_1_1_, pc.post_id AS post_id4_1_0__, pc.id AS id1_1_0__ FROM post p LEFT OUTER JOIN post_comment pc ON p.id=pc.post_id WHERE p.title LIKE ? ORDER BY p.created_on "], Params:[( 'High-Performance Java Persistence %' )]
Первое, что следует заметить,-это предупреждающее сообщение HHH000104
, которое указывает, что разбиение на страницы было выполнено в памяти. Инструкция SQL подтверждает это, поскольку для ограничения размера результирующего набора не используется предложение LIMIT.
Если имеется 100 записей
, соответствующих предоставленному заголовку
шаблону, и каждая запись
содержит около 50 комментариев, то этот результирующий набор будет содержать 5000 записей, которые будут извлечены Hibernate, только для удаления после достижения предусмотренного порога в 5 Записей
сущностей.
Включение режима гибернации.запрос.конфигурация fail_on_pagination_over_collection_fetch
Начиная с Hibernate ORM 5.2.13, теперь вы можете включить hibernate.query.fail_on_pagination_over_collection_fetch
свойство конфигурации следующим образом:
Таким образом, при выполнении предыдущего запроса JPQL:
try { 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(); fail("Should have thrown Exception"); } catch (Exception e) { assertTrue( e.getMessage().contains( "In memory pagination was about to be applied" ) ); }
Исключение HibernateException
выдается вместо простого сообщения о предупреждении. Таким образом, вы получите мгновенную обратную связь об этих проблемах с разбиением на страницы в памяти и устраните их задолго до того, как они попадут в производственные системы.
Этот параметр используется не только для запросов JPQL, но и для запросов API критериев.
Написание запросов API критериев JPA не очень просто. Плагин Codota IDE может помочь вам в написании таких запросов, что повысит вашу производительность.
Для получения более подробной информации о том, как вы можете использовать Codota для ускорения процесса написания запросов API критериев, ознакомьтесь с этой статьей .
try { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuerycriteria = builder.createQuery(Post.class); Root post = criteria.from(Post.class); post.fetch(Post_.comments); ParameterExpression parameterExpression = builder.parameter(String.class); criteria.where( builder.like( post.get(Post_.title), parameterExpression ) ) .orderBy( builder.asc( post.get(Post_.createdOn) ) ); entityManager .createQuery(criteria) .setParameter( parameterExpression, "High-Performance Java Persistence %" ) .setMaxResults(5) .getResultList(); fail("Should have thrown Exception"); } catch (Exception e) { assertTrue( e.getMessage().contains( "In memory pagination was about to be applied" ) ); }
Круто, правда?
Вывод
Спящий режим.свойство конфигурации query.fail_on_pagination_over_collection_fetch
очень полезно, и оно обязательно должно быть включено, если вы используете версию ORM для гибернации, которая превышает 5.2.13.