Автор оригинала: 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();
CriteriaQuery criteria = 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.