Автор оригинала: Vlad Mihalcea.
На форуме Hibernate я заметил следующий вопрос , который касается использования кэша запросов Hibernate для хранения В проекциях, а не в сущностях.
В то время как кэширование JPQL-запросов, в которых выбираются сущности, довольно типично, кэширование В проекции является менее известной функцией кэша запросов второго уровня Hibernate.
Давайте предположим, что у нас есть два Сообщения
и Комментарий к сообщению
сущности, которые выглядят следующим образом:
Теперь для главной страницы нашего веб-сайта нам просто нужно отобразить сводку последних Сообщений
записей и указать количество связанных отправлений
дочерних объектов.
Однако мы не хотим извлекать все Сообщения
вместе с их соответствующими Комментариями
по двум причинам:
- Мы можем столкнуться с HHH000104: firstResult/maxResults, указанными при извлечении коллекции; применение в памяти! выпуск
- Мы не хотим получать больше столбцов, чем необходимо, потому что это не очень эффективно.
Итак, для нашей главной страницы мы просто выберем краткое изложение, которое может быть представлено следующим образом::
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:
ListgetLatestPostSummaries( 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 -> { ListlatestPosts = 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 -> { ListlatestPosts = 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-запросы, но базовые таблицы не часто меняются.