Рубрики
Без рубрики

Как обнаружить проблемы с HHH000104 в режиме гибернации.запрос.fail_on_pagination_over_collection_fetch

Узнайте, как предотвратить проблемы с HHH000104, включив режим гибернации.запрос.свойство конфигурации гибернации fail_on_pagination_over_collection_fetch.

Автор оригинала: 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:

List 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();

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.