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

Как использовать кэш запросов Hibernate для проекций DTO

Как использовать кэш запросов Hibernate для хранения и извлечения прогнозов, полученных в результате выполнения собственных SQL-запросов.

Автор оригинала: Vlad Mihalcea.

На форуме Hibernate я заметил следующий вопрос , который касается использования кэша запросов Hibernate для хранения В проекциях, а не в сущностях.

В то время как кэширование JPQL-запросов, в которых выбираются сущности, довольно типично, кэширование В проекции является менее известной функцией кэша запросов второго уровня Hibernate.

Давайте предположим, что у нас есть два Сообщения и Комментарий к сообщению сущности, которые выглядят следующим образом:

Теперь для главной страницы нашего веб-сайта нам просто нужно отобразить сводку последних Сообщений записей и указать количество связанных отправлений дочерних объектов.

Однако мы не хотим извлекать все Сообщения вместе с их соответствующими Комментариями по двум причинам:

  1. Мы можем столкнуться с HHH000104: firstResult/maxResults, указанными при извлечении коллекции; применение в памяти! выпуск
  2. Мы не хотим получать больше столбцов, чем необходимо, потому что это не очень эффективно.

Итак, для нашей главной страницы мы просто выберем краткое изложение, которое может быть представлено следующим образом::

public class PostSummary {

    private Long id;

    private String title;

    private Date createdOn;

    private int commentCount;

    public PostSummary(
            Long id, 
            String title, 
            Date createdOn, 
            Number commentCount) {
        this.id = id;
        this.title = title;
        this.createdOn = createdOn;
        this.commentCount = commentCount.intValue();
    }

    public Long getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public Date getCreatedOn() {
        return createdOn;
    }

    public int getCommentCount() {
        return commentCount;
    }

    @Override
    public String toString() {
        return "PostSummary{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", createdOn=" + createdOn +
                ", commentCount=" + commentCount +
                '}';
    }
}

Чтобы получить последнюю Публикацию Резюме DTO, мы собираемся использовать следующий запрос проекции JPQL:

List getLatestPostSummaries(
            EntityManager entityManager,
            int maxResults,
            boolean cacheable) {
    List latestPosts = entityManager.createQuery(
        "select new " +
        "   com.vladmihalcea.book.hpjp.hibernate.cache.query.PostSummary(" +
        "       p.id, " +
        "       p.title, " +
        "       p.createdOn, " +
        "       count(pc.id) " +
        "   ) " +
        "from PostComment pc " +
        "left join pc.post p " +
        "group by p.id, p.title " +
        "order by p.createdOn desc ", PostSummary.class)
    .setMaxResults(maxResults)
    .setHint(QueryHints.HINT_CACHEABLE, cacheable)
    .getResultList();

    LOGGER.debug("Latest posts: {}", latestPosts);

    return latestPosts;
}

Итак, в этом запросе используется несколько конструкций, о которых стоит упомянуть:

  • предложение SELECT использует результат конструктора ДЛЯ проецирования, поэтому запрос возвращает Список объектов Post Summary .
  • метод setMaxResults используется для ограничения размера базового набора результатов SQL
  • подсказка HINT_CACHEABLE JPA предназначена для кэширования набора результатов

Теперь, если мы вызовем этот метод без кэширования:

doInJPA(entityManager -> {
    List latestPosts = getLatestPostSummaries(
        entityManager,
        5,
        false
    );

    assertEquals(5, latestPosts.size());
});

Мы видим, что Hibernate генерирует правильный результат:

SELECT 
    p.id AS col_0_0_,
    p.title AS col_1_0_,
    p.created_on AS col_2_0_,
    count(pc.id) AS col_3_0_
FROM 
    post_comment pc
LEFT OUTER JOIN 
    post p 
    ON pc.post_id=p.id
GROUP BY 
    p.id,
    p.title
ORDER BY 
    p.created_on DESC
LIMIT 
    5

-- Latest posts: [
    PostSummary{
        id=42, 
        title='High-Performance Java Persistence, Chapter 10', 
        createdOn=2018-02-07 12:09:53.691, 
        commentCount=6
    }, 
    PostSummary{
        id=40, 
        title='High-Performance Java Persistence, Chapter 9', 
        createdOn=2018-02-07 12:09:53.69, 
        commentCount=1
    }, 
    PostSummary{
        id=35, 
        title='High-Performance Java Persistence, Chapter 8', 
        createdOn=2018-02-07 12:09:53.686, 
        commentCount=4
    }, 
    PostSummary{
        id=30, 
        title='High-Performance Java Persistence, Chapter 7', 
        createdOn=2018-02-07 12:09:53.68, commentCount=4
    }, 
    PostSummary{
        id=19, title='High-Performance Java Persistence, Chapter 6', 
        createdOn=2018-02-07 12:09:53.67, 
        commentCount=9
    }
]

Теперь, если мы вызовем этот метод еще раз, Hibernate выполнит тот же SQL-запрос:

SELECT 
    p.id AS col_0_0_,
    p.title AS col_1_0_,
    p.created_on AS col_2_0_,
    count(pc.id) AS col_3_0_
FROM 
    post_comment pc
LEFT OUTER JOIN 
    post p 
    ON pc.post_id=p.id
GROUP BY 
    p.id,
    p.title
ORDER BY 
    p.created_on DESC
LIMIT 
    5

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

Итак, если мы передадим кэшируемый параметр , установленный в true , Hibernate сможет кэшировать запрос, и мы увидим это в области кэша запросов:

Итак, при выполнении этого тестового случая:

doInJPA(entityManager -> {
    List latestPosts = getLatestPostSummaries(
        entityManager,
        5,
        true
    );

    printQueryCacheRegionStatistics();
    assertEquals(5, latestPosts.size());
});

Hibernate создаст следующий вывод:

-- Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache
-- key: sql: select querycache1_.id as col_0_0_, querycache1_.title as col_1_0_, querycache1_.created_on as col_2_0_, count(querycache0_.id) as col_3_0_ from post_comment querycache0_ left outer join post querycache1_ on querycache0_.post_id=querycache1_.id group by querycache1_.id , querycache1_.title order by querycache1_.created_on desc; parameters: ; named parameters: {}; max rows: 5; transformer: org.hibernate.transform.CacheableResultTransformer@47bbf55f
-- Element for key sql: select querycache1_.id as col_0_0_, querycache1_.title as col_1_0_, querycache1_.created_on as col_2_0_, count(querycache0_.id) as col_3_0_ from post_comment querycache0_ left outer join post querycache1_ on querycache0_.post_id=querycache1_.id group by querycache1_.id , querycache1_.title order by querycache1_.created_on desc; parameters: ; named parameters: {}; max rows: 5; transformer: org.hibernate.transform.CacheableResultTransformer@47bbf55f is null
-- Query results were not found in cache

SELECT 
    p.id AS col_0_0_,
    p.title AS col_1_0_,
    p.created_on AS col_2_0_,
    count(pc.id) AS col_3_0_
FROM 
    post_comment pc
LEFT OUTER JOIN 
    post p 
    ON pc.post_id=p.id
GROUP BY 
    p.id,
    p.title
ORDER BY 
    p.created_on DESC
LIMIT 
    5

-- Caching query results in region: org.hibernate.cache.internal.StandardQueryCache; timestamp=6217724081721344
-- key: sql: select querycache1_.id as col_0_0_, querycache1_.title as col_1_0_, querycache1_.created_on as col_2_0_, count(querycache0_.id) as col_3_0_ from post_comment querycache0_ left outer join post querycache1_ on querycache0_.post_id=querycache1_.id group by querycache1_.id , querycache1_.title order by querycache1_.created_on desc; parameters: ; named parameters: {}; max rows: 5; transformer: org.hibernate.transform.CacheableResultTransformer@47bbf55f value: [6217724081721344, [Ljava.io.Serializable;@621f23ac, [Ljava.io.Serializable;@7761e342, [Ljava.io.Serializable;@51f68849, [Ljava.io.Serializable;@4eb9ae4d, [Ljava.io.Serializable;@5520f675]

Итак, результат был кэширован, и если мы попытаемся загрузить его еще раз, Hibernate пропустит выполнение SQL-запроса:

-- Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache
-- key: sql: select querycache1_.id as col_0_0_, querycache1_.title as col_1_0_, querycache1_.created_on as col_2_0_, count(querycache0_.id) as col_3_0_ from post_comment querycache0_ left outer join post querycache1_ on querycache0_.post_id=querycache1_.id group by querycache1_.id , querycache1_.title order by querycache1_.created_on desc; parameters: ; named parameters: {}; max rows: 5; transformer: org.hibernate.transform.CacheableResultTransformer@47bbf55f
-- Checking query spaces are up-to-date: [post, post_comment]
-- Returning cached query results

Круто, правда?

Хотя использование кэша запросов Hibernate для сущностей не является редкостью, кэш запросов второго уровня работает также для проекций DTO, и он может подойти, если у нас часто выполняются SQL-запросы, но базовые таблицы не часто меняются.