Вступление
Теперь, когда я рассмотрел кэширование сущностей и коллекций , пришло время изучить, как работает Query Caching .
Кэш запросов строго связан с сущностями и устанавливает связь между критериями поиска и сущностями, выполняющими этот конкретный фильтр запросов. Как и другие функции Hibernate, Query Cache не так тривиален, как можно подумать.
Модель объекта
Для наших тестовых случаев мы будем использовать следующую модель предметной области:
Объект Post имеет связь « многие к одному» с автором, и оба объекта хранятся в кэше второго уровня.
Включение кеша запросов
Кэш запросов по умолчанию отключен, и для его активации нам необходимо предоставить следующее свойство Hibernate:
1
2
|
properties.put( "hibernate.cache.use_query_cache" , Boolean.TRUE.toString()); |
Чтобы Hibernate кэшировал данный результат запроса, нам нужно явно установить атрибут запроса cachable при создании запроса.
Прочитанное кеширование
Кэш запросов является читаемым и, подобно стратегии параллелизма NONSTRICT_READ_WRITE , может аннулировать только устаревшие записи.
В следующем примере мы собираемся кэшировать следующий запрос:
1
2
3
4
5
6
7
8
9
|
private List<Post> getLatestPosts(Session session) { return (List<Post>) session.createQuery( "select p " + "from Post p " + "order by p.createdOn desc" ) .setMaxResults( 10 ) .setCacheable( true ) .list(); } |
Во-первых, мы собираемся исследовать внутреннюю структуру Query Cache, используя следующий тестовый пример:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
doInTransaction(session -> { LOGGER.info( "Evict regions and run query" ); session.getSessionFactory() .getCache().evictAllRegions(); assertEquals( 1 , getLatestPosts(session).size()); }); doInTransaction(session -> { LOGGER.info( "Check get entity is cached" ); Post post = (Post) session.get(Post. class , 1L); }); doInTransaction(session -> { LOGGER.info( "Check query result is cached" ); assertEquals( 1 , getLatestPosts(session).size()); }); |
Этот тест генерирует следующий вывод:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
QueryCacheTest - Evict regions and run query StandardQueryCache - Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache EhcacheGeneralDataRegion - Element for key sql: select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ order by querycache0_.created_on desc; parameters: ; named parameters: {}; max rows: 10 ; transformer: org.hibernate.transform.CacheableResultTransformer @110f2 is null StandardQueryCache - Query results were not found in cache select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ order by querycache0_.created_on desc limit 10 StandardQueryCache - Caching query results in region: org.hibernate.cache.internal.StandardQueryCache; timestamp= 5872026465492992 EhcacheGeneralDataRegion - key: sql: select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ order by querycache0_.created_on desc; parameters: ; named parameters: {}; max rows: 10 ; transformer: org.hibernate.transform.CacheableResultTransformer @110f2 value: [ 5872026465492992 , 1 ] JdbcTransaction - committed JDBC Connection ------------------------------------------------------------ QueryCacheTest - Check get entity is cached JdbcTransaction - committed JDBC Connection ------------------------------------------------------------ QueryCacheTest - Check query is cached StandardQueryCache - Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache StandardQueryCache - Checking query spaces are up-to-date: [Post] EhcacheGeneralDataRegion - key: Post UpdateTimestampsCache - [Post] last update timestamp: 5872026465406976 , result set timestamp: 5872026465492992 StandardQueryCache - Returning cached query results JdbcTransaction - committed JDBC Connection |
- Все области кэша удалены, чтобы убедиться, что кэш пуст
- После выполнения запроса Post кэш запросов проверяет ранее сохраненные результаты.
- Поскольку запись в кэше отсутствует, запрос отправляется в базу данных.
- Как выбранные объекты, так и результат запроса кэшируются
- Затем мы проверяем, что сущность Post была сохранена в кэше второго уровня.
- Последующий запрос запроса будет решен из кэша, не затрагивая базу данных
Параметры запроса
Параметры запроса встраиваются в ключ записи в кеш, как мы можем видеть в следующих примерах.
Основные типы
Во-первых, мы собираемся использовать базовую фильтрацию типов :
01
02
03
04
05
06
07
08
09
10
11
12
|
private List<Post> getLatestPostsByAuthorId(Session session) { return (List<Post>) session.createQuery( "select p " + "from Post p " + "join p.author a " + "where a.id = :authorId " + "order by p.createdOn desc" ) .setParameter( "authorId" , 1L) .setMaxResults( 10 ) .setCacheable( true ) .list(); } |
1
2
3
4
5
|
doInTransaction(session -> { LOGGER.info( "Query cache with basic type parameter" ); List<Post> posts = getLatestPostsByAuthorId(session); assertEquals( 1 , posts.size()); }); |
Запись Query Cache выглядит следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
EhcacheGeneralDataRegion - key: sql: select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ inner join Author querycache1_ on querycache0_.author_id=querycache1_.id where querycache1_.id=? order by querycache0_.created_on desc; parameters: ; named parameters: {authorId= 1 }; max rows: 10 ; transformer: org.hibernate.transform.CacheableResultTransformer @110f2 value: [ 5871781092679680 , 1 ] |
Параметр хранится в ключе записи кеша. Первым элементом значения записи в кеше всегда является метка времени выборки из результирующего набора. Следующие элементы являются идентификаторами сущностей, которые были возвращены этим запросом.
Типы сущностей
Мы также можем использовать типы сущностей в качестве параметров запроса:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
private List<Post> getLatestPostsByAuthor(Session session) { Author author = (Author) session.get(Author. class , 1L); return (List<Post>) session.createQuery( "select p " + "from Post p " + "join p.author a " + "where a = :author " + "order by p.createdOn desc" ) .setParameter( "author" , author) .setMaxResults( 10 ) .setCacheable( true ) .list(); } |
1
2
3
4
5
|
doInTransaction(session -> { LOGGER.info( "Query cache with entity type parameter" ); List<Post> posts = getLatestPostsByAuthor(session); assertEquals( 1 , posts.size()); }); |
Запись в кеш аналогична нашему предыдущему примеру, поскольку Hibernate хранит только идентификатор объекта в ключе записи в кеш. Это имеет смысл, поскольку Hibernate уже кэширует сущность Author .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
EhcacheGeneralDataRegion - key: sql: select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ inner join Author querycache1_ on querycache0_.author_id=querycache1_.id where querycache1_.id=? order by querycache0_.created_on desc; parameters: ; named parameters: {author= 1 }; max rows: 10 ; transformer: org.hibernate.transform.CacheableResultTransformer @110f2 value: [ 5871781092777984 , 1 ] |
консистенция
HQL / JPQL-запрос недействителен
Hibernate кэш второго уровня поддерживает строгую согласованность, и Query Cache ничем не отличается. Как и при очистке , Query Cache может сделать недействительными свои записи при изменении связанного табличного пространства. Каждый раз, когда мы сохраняем / удаляем / обновляем Entity , все записи в Query Cache, использующие эту конкретную таблицу, становятся недействительными
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
doInTransaction(session -> { Author author = (Author) session.get(Author. class , 1L); assertEquals( 1 , getLatestPosts(session).size()); LOGGER.info( "Insert a new Post" ); Post newPost = new Post( "Hibernate Book" , author); session.persist(newPost); session.flush(); LOGGER.info( "Query cache is invalidated" ); assertEquals( 2 , getLatestPosts(session).size()); }); doInTransaction(session -> { LOGGER.info( "Check Query cache" ); assertEquals( 2 , getLatestPosts(session).size()); }); |
Этот тест добавит новое сообщение, а затем повторно запустит кешируемый запрос. Запуск этого теста дает следующий вывод:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
QueryCacheTest - Insert a new Post insert into Post (id, author_id, created_on, name) values ( default , 1 , '2015-06-06 17:29:59.909' , 'Hibernate Book' ) UpdateTimestampsCache - Pre-invalidating space [Post], timestamp: 5872029941395456 EhcacheGeneralDataRegion - key: Post value: 5872029941395456 QueryCacheTest - Query cache is invalidated StandardQueryCache - Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache EhcacheGeneralDataRegion - key: sql: select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ order by querycache0_.created_on desc; parameters: ; named parameters: {}; max rows: 10 ; transformer: org.hibernate.transform.CacheableResultTransformer @110f2 StandardQueryCache - Checking query spaces are up-to-date: [Post] EhcacheGeneralDataRegion - key: Post UpdateTimestampsCache - [Post] last update timestamp: 5872029941395456 , result set timestamp: 5872029695619072 StandardQueryCache - Cached query results were not up-to-date select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ order by querycache0_.created_on desc limit 10 StandardQueryCache - Caching query results in region: org.hibernate.cache.internal.StandardQueryCache; timestamp= 5872029695668224 EhcacheGeneralDataRegion - key: sql: select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ order by querycache0_.created_on desc; parameters: ; named parameters: {}; max rows: 10 ; transformer: org.hibernate.transform.CacheableResultTransformer @110f2 value: [ 5872029695668224 , 2 , 1 ] JdbcTransaction - committed JDBC Connection UpdateTimestampsCache - Invalidating space [Post], timestamp: 5872029695680512 EhcacheGeneralDataRegion - key: Post value: 5872029695680512 ------------------------------------------------------------ QueryCacheTest - Check Query cache StandardQueryCache - Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache EhcacheGeneralDataRegion - key: sql: select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ order by querycache0_.created_on desc; parameters: ; named parameters: {}; max rows: 10 ; transformer: org.hibernate.transform.CacheableResultTransformer @110f2 StandardQueryCache - Checking query spaces are up-to-date: [Post] EhcacheGeneralDataRegion - key: Post UpdateTimestampsCache - [Post] last update timestamp: 5872029695680512 , result set timestamp: 5872029695668224 StandardQueryCache - Cached query results were not up-to-date select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ order by querycache0_.created_on desc limit 10 StandardQueryCache - Caching query results in region: org.hibernate.cache.internal.StandardQueryCache; timestamp= 5872029695705088 EhcacheGeneralDataRegion - key: sql: select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ order by querycache0_.created_on desc; parameters: ; named parameters: {}; max rows: 10 ; transformer: org.hibernate.transform.CacheableResultTransformer @110f2 value: [ 5872029695705088 , 2 , 1 ] JdbcTransaction - committed JDBC Connection |
- Как только Hibernate обнаруживает изменение состояния сущности , он предварительно аннулирует уязвимые области кэша запросов
- Запись Query Cache не удаляется, но соответствующая метка времени обновляется.
- Кэш запросов всегда проверяет временную метку ключа ввода и пропускает чтение его значения, если временная метка ключа новее, чем временная метка загрузки результирующего набора
- Если текущий сеанс повторно запускает этот запрос, результат будет снова кэширован
- Текущая транзакция базы данных фиксируется, и изменения распространяются от изоляции на уровне сеанса к общей согласованности чтения
- Происходит фактическая аннулирование, и отметка времени записи в кэше снова обновляется
Этот подход может нарушить гарантии согласованности READ COMMITTED, поскольку возможно грязное чтение , поскольку текущие изолированные изменения передаются в кэш до фиксации транзакции базы данных.
Неверный собственный запрос
Как я уже говорил ранее , собственные запросы оставляют Hibernate в темноте, поскольку он не может знать, какие таблицы в конечном итоге могут изменить собственные запросы. В следующем тесте мы собираемся обновить таблицу Author , проверяя ее влияние на текущий кеш Post Query:
01
02
03
04
05
06
07
08
09
10
11
|
doInTransaction(session -> { assertEquals( 1 , getLatestPosts(session).size()); LOGGER.info( "Execute native query" ); assertEquals( 1 , session.createSQLQuery( "update Author set name = '\"'||name||'\"' " ).executeUpdate()); LOGGER.info( "Check query cache is invalidated" ); assertEquals( 1 , getLatestPosts(session).size()); }); |
Тест генерирует следующий вывод:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
QueryCacheTest - Execute native query UpdateTimestampsCache - Pre-invalidating space [Author], timestamp: 5872035446091776 EhcacheGeneralDataRegion - key: Author value: 5872035446091776 UpdateTimestampsCache - Pre-invalidating space [Post], timestamp: 5872035446091776 EhcacheGeneralDataRegion - key: Post value: 5872035446091776 update Author set name = '"' ||name|| '"' QueryCacheTest - Check query cache is invalidated StandardQueryCache - Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache EhcacheGeneralDataRegion - key: sql: select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ order by querycache0_.created_on desc; parameters: ; named parameters: {}; max rows: 10 ; transformer: org.hibernate.transform.CacheableResultTransformer @110f2 StandardQueryCache - Checking query spaces are up-to-date: [Post] EhcacheGeneralDataRegion - key: Post UpdateTimestampsCache - [Post] last update timestamp: 5872035446091776 , result set timestamp: 5872035200290816 StandardQueryCache - Cached query results were not up-to-date select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ order by querycache0_.created_on desc limit 10 StandardQueryCache - Caching query results in region: org.hibernate.cache.internal.StandardQueryCache; timestamp= 5872035200364544 EhcacheGeneralDataRegion - key: sql: select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ order by querycache0_.created_on desc; parameters: ; named parameters: {}; max rows: 10 ; transformer: org.hibernate.transform.CacheableResultTransformer @110f2 value: [ 5872035200364544 , 1 ] JdbcTransaction - committed JDBC Connection UpdateTimestampsCache - Invalidating space [Post], timestamp: 5872035200372736 EhcacheGeneralDataRegion - key: Post value: 5872035200372736 UpdateTimestampsCache - Invalidating space [Author], timestamp: 5872035200372736 EhcacheGeneralDataRegion - key: Author value: 5872035200372736 |
Обе области Автор и Почтовый кеш были признаны недействительными, даже если была изменена только таблица Автор . Чтобы это исправить, нам нужно дать Hibernate знать, какие таблицы мы собираемся изменить.
Синхронизация областей собственного кэша запросов
Hibernate позволяет нам определять табличное пространство запроса с помощью подсказок по синхронизации запросов . При предоставлении этой информации Hibernate может сделать недействительными запрошенные области кэша:
01
02
03
04
05
06
07
08
09
10
11
12
|
doInTransaction(session -> { assertEquals( 1 , getLatestPosts(session).size()); LOGGER.info( "Execute native query with synchronization" ); assertEquals( 1 , session.createSQLQuery( "update Author set name = '\"'||name||'\"' " ).addSynchronizedEntityClass(Author. class ) .executeUpdate()); LOGGER.info( "Check query cache is not invalidated" ); assertEquals( 1 , getLatestPosts(session).size()); }); |
Создается следующий вывод:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
QueryCacheTest - Execute native query with synchronization UpdateTimestampsCache - Pre-invalidating space [Author], timestamp: 5872036893995008 EhcacheGeneralDataRegion - key: Author value: 5872036893995008 update Author set name = '"' ||name|| '"' QueryCacheTest - Check query cache is not invalidated StandardQueryCache - Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache EhcacheGeneralDataRegion - key: sql: select querycache0_.id as id1_1_, querycache0_.author_id as author_i4_1_, querycache0_.created_on as created_2_1_, querycache0_.name as name3_1_ from Post querycache0_ order by querycache0_.created_on desc; parameters: ; named parameters: {}; max rows: 10 ; transformer: org.hibernate.transform.CacheableResultTransformer @110f2 StandardQueryCache - Checking query spaces are up-to-date: [Post] EhcacheGeneralDataRegion - key: Post UpdateTimestampsCache - [Post] last update timestamp: 5872036648169472 , result set timestamp: 5872036648226816 StandardQueryCache - Returning cached query results JdbcTransaction - committed JDBC Connection UpdateTimestampsCache - Invalidating space [Author], timestamp: 5872036648263680 EhcacheGeneralDataRegion - key: Author value: 5872036648263680 |
Только предоставленное табличное пространство было признано недействительным, оставив кэш Post Query нетронутым. Смешивание собственных запросов и кеширования запросов возможно, но требует немного усердия.
Вывод
Кэш запросов может повысить производительность приложения для часто выполняемых запросов сущностей, но это не бесплатная поездка. Он подвержен проблемам согласованности и без надлежащего механизма управления памятью может легко стать достаточно большим.
Код доступен на GitHub .
Ссылка: | Как работает Hibernate Query Cache от нашего партнера по JCG Влада Михалча в блоге Влада Михалча . |