Автор оригинала: Vlad Mihalcea.
Теперь, когда я рассмотрел как Сущность , так и Коллекцию кэширование, пришло время изучить, как работает Кэширование запросов .
Кэш запросов строго связан с Сущностями и устанавливает связь между критерием поиска и Сущностями, выполняющими этот конкретный фильтр запросов. Как и другие функции гибернации, Кэш запросов не так тривиален, как можно было бы подумать.
Для наших тестовых случаев мы будем использовать следующую модель предметной области:
Сущность Post имеет связь многие к одному с Автором , и обе сущности хранятся в кэше второго уровня.
Кэш запросов по умолчанию отключен, и для его активации нам необходимо указать следующее свойство Hibernate:
properties.put("hibernate.cache.use_query_cache", Boolean.TRUE.toString());
Чтобы Hibernate кэшировал данный результат запроса, нам необходимо явно задать атрибут кэшируемый запрос при создании запроса.
Кэш запросов доступен для чтения , и, как и стратегия параллелизма NONSTRICT_READ_WRITE, он может только аннулировать устаревшие записи.
В следующем примере мы собираемся кэшировать следующий запрос:
private ListgetLatestPosts(Session session) { return (List ) session.createQuery( "select p " + "from Post p " + "order by p.createdOn desc") .setMaxResults(10) .setCacheable(true) .list(); }
Во-первых, мы собираемся исследовать внутреннюю структуру кэша запросов, используя следующий тестовый случай:
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()); });
Этот тест генерирует следующие выходные данные:
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 был сохранен в кэше второго уровня
- Последующий запрос запроса будет разрешен из кэша без попадания в базу данных
Параметры запроса встроены в ключ ввода кэша, как мы можем видеть в следующих примерах.
Основные типы
Во-первых, мы собираемся использовать базовую фильтрацию типов:
private ListgetLatestPostsByAuthorId(Session session) { return (List ) 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(); }
doInTransaction(session -> { LOGGER.info("Query cache with basic type parameter"); Listposts = getLatestPostsByAuthorId(session); assertEquals(1, posts.size()); });
Запись в кэше запросов выглядит следующим образом:
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]
Параметр хранится в ключе ввода кэша. Первым элементом значения записи в кэше всегда является метка времени выборки результирующего набора. Следующие элементы являются идентификаторами сущностей, которые были возвращены этим запросом.
Типы сущностей
Мы также можем использовать типы сущностей в качестве параметров запроса:
private ListgetLatestPostsByAuthor(Session session) { Author author = (Author) session.get(Author.class, 1L); return (List ) 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(); }
doInTransaction(session -> { LOGGER.info("Query cache with entity type parameter"); Listposts = getLatestPostsByAuthor(session); assertEquals(1, posts.size()); });
Запись в кэше аналогична нашему предыдущему примеру, поскольку Hibernate сохранил идентификатор сущности только в ключе записи в кэше. Это имеет смысл, поскольку Hibernate уже кэширует сущность Author .
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
Кэш второго уровня спящего режима обеспечивает высокую согласованность, и кэш запросов ничем не отличается. Как и при сбросе , кэш запросов может аннулировать свои записи всякий раз, когда изменяется связанное табличное пространство. Каждый раз , когда мы сохраняем/удаляем/обновляем Сущность , все записи кэша запросов, использующие эту конкретную таблицу, будут признаны недействительными.
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()); });
Этот тест добавит новый Post , а затем повторно запустит кэшируемый запрос. Выполнение этого теста дает следующий результат:
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 обнаруживает переход состояния сущности , он предварительно делает недействительными затронутые области кэша запросов
- Запись кэша запросов не удаляется, но соответствующая ей метка времени обновляется
- Кэш запросов всегда проверяет временную метку ключа ввода и пропускает считывание ее значения, если временная метка ключа новее, чем временная метка загрузки результирующего набора
- Если текущий сеанс повторит этот запрос, результат будет снова кэширован
- Текущая транзакция базы данных фиксирует
- Фактическое аннулирование происходит, и метка времени табличного пространства обновляется в соответствии с меткой времени фиксации транзакции
Недействительность собственного запроса
Как я ранее заявлял , собственные запросы оставляют Hibernate в неведении, поскольку он не может знать, какие таблицы в конечном итоге может изменить собственный запрос. В следующем тесте мы собираемся обновить таблицу Author , проверяя ее влияние на текущий кэш Post запросов:
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()); });
Тест генерирует следующие выходные данные:
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 может аннулировать запрошенные области кэша:
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()); });
Генерируется следующий вывод:
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 Запросов нетронутым. Смешивание собственных запросов и кэширования запросов возможно, но для этого требуется немного усердия.
Кэш запросов может повысить производительность приложения для часто выполняемых запросов сущностей, но это не бесплатная поездка. Он подвержен проблемам согласованности, и без надлежащего механизма управления памятью он может легко вырасти довольно большим.
Код доступен на GitHub .