Автор оригинала: 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_CACHEABLEJPA предназначена для кэширования набора результатов
Теперь, если мы вызовем этот метод без кэширования:
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-запросы, но базовые таблицы не часто меняются.