[Из песочницы] Документация разработчика Hibernate – Глава VI. Кэширование

Представляю вашему вниманию перевод шестой главы официальной документации Hibernate.

Что собой представляют кэш первого и второго уровня в Hibernate, показано на следующий диаграмме (прим. автора).

image

6.1. Кэш запросов


Если у вас есть запросы, выполняющиеся снова и снова, с одними и теми же параметрами, кэширование запросов предоставит выигрыш в производительности.

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

Процедура 6.1. Разрешение кэширования запросов


  1. Выставьте свойство hibernate.cache.use_query_cache в true.

    Эта настройка создаст два новых региона кэширования:

    • org.hibernate.cache.internal.StandardQueryCache хранит результаты кэшированных запросов.
    • org.hibernate.cache.spi.UpdateTimestampsCache хранит временные метки недавних апдейтов в запрашиваемые таблицы. Эти временные метки валидируют результаты, берущиеся из кэша запроса (т.е. через них проверяется актуальность объектов, — прим.перев.)

  2. Настройте таймаут региона кэширования

    Если вы конфигурируете таймауты или окончание срока в вашей реализации кэша, выставьте таймаут нижележащего кэш-региона для UpdateTimestampsCache на большее значение, чем таймауты любого из кэшей запросов. Вполне возможно, и рекомендуется, выставлять бесконечный таймаут истечения у региона UpdateTimestampsCache. Если быть более конкретным, LRU-политика кэширования (Least Recently Used) не считается подходящей.

  3. Разрешите кэширование результатов для конкретных запросов

    Так как большая часть запросов не выигрывает от кэширования, вам нужно разрешить кэширование только для индивидуальных запросов, даже после повсеместного разрешения кэширования. Для того, чтобы разрешить кэширование для определенного запроса, вызовите org.hibernate.Query.setCacheable(true). Вызов позволит запросу ”заглянуть” в кэш перед выполнением, или положить туда результаты после выполнения.


Кэш запросов не кэширует состояние актуальных сущностей в кэше. Он кэширует значения идентификаторов и результаты типов. Таким образом, всегда используете кэш запросов в паре с кэшем второго уровня для тех сущностей, которые должны кэшироваться как часть кэша запросов.

6.1.1. Регионы кэша запросов


Для лучшего контроля за политиками истечения актуальности у кэшей запросов, укажите именованный регион кэширования для определенного запроса, вызвав Query.setCacheRegion().

Пример 6.1. Метод setCacheRegion

List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
        .setEntity("blogger", blogger)
        .setMaxResults(15)
        .setCacheable(true)
        .setCacheRegion("frontpages")
        .list();


Чтобы заставить кэш запросов обновить один из его регионов, и проигнорировать любые закэшированные данные, вызовите org.hibernate.Query.setCacheMode(CacheMode.REFRESH). В паре с регионом, определенным для запроса, Hibernate выборочно обновит результаты, закэшированные в данном регионе. Это более эффективно, нежели групповое удаление записей (bulk eviction) из региона c помощью org.hibernate.SessionFactory.evictQueries().

6.2. Провайдеры кэша второго уровня


Hibernate совместим с некоторыми провайдерами кэша второго уровня. Ни один из провайдеров не поддерживает абсолютно все возможные стратегии кэширования, определенные в Hibernate. Секция 6.2.3, “Провайдеры кэша второго уровня для Hibernate” содержит список провайдеров, вместе с их интерфейсами и поддерживаемыми стратегиями кэширования. Для определений стратегий кэширования, см. Секцию 6.2.2, “Стратегии кэширования”.

6.2.1. Конфигурация пользовательских кэш-провайдеров


Вы можете конфигурировать ваши кэш-провайдеры, используя аннотации или файлы маппинга. Сущности, по-умолчанию, не являются частью кэша второго уровня, и их использование не рекомендуется. Если вам все равно необходимо использовать сущности, установите элемент shared-cache-mode в persistence.xml, или используйте свойство javax.persistence.sharedCache.mode в вашей конфигурации. Используйте одно из значений Таблицы 6.1, “Возможные значения для Shared Cache Mode”.

Таблица 6.1. Возможные значения для Shared Cache Mode

Значение Описание
ENABLE_SELECTIVE Сущности не будут кэшироваться, до тех пор, пока вы явно не пометите их как кэшируемые.
Это значение рекомендуется и используется по-умолчанию.
DISABLE_SELECTIVE Сущности будут кэшироваться, до тех пор, пока вы явно не отмените кэширование на них.
ALL Все сущности будут кэшироваться, в независимости от того, пометите ли вы их как некэшируемые.
NONE Ни одна сущность не будет кэшироваться, даже если вы пометите их как кэшируемые. Эта опция отменяет
работу кэша второго уровня.


Глобальная concurrency-стратегия устанавливается с помощью свойства hibernate.cache.default_cache_concurrency_strategy. См. возможные значения в секции 6.2.2, “Стратегии кэширования”.

Важно

Когда это возможно, определяйте concurrency-стратегию кэширования только для определенной сущности, но не глобально. Используйте аннотацию @org.hibernate.annotations.Cache.


Пример 6.2. Конфигурация кэш-провайдеров с использованием аннотаций.

@Entity 
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Forest { ... }


Вы можете кэшировать содержимое коллекций или идентификаторов, если коллекция содержит другие сущности. Используйте аннотацию @Cache на свойстве коллекции.

@Cache содержит несколько атрибутов.

Атрибуты аннотации @Cache:

Имя Описание
usage Заданная concurrency-стратегия кэша, которая может быть:
  • NONE
  • READ_ONLY
  • NONSTRICT_READ_WRITE
  • READ_WRITE
  • TRANSACTIONAL

region Регион кэширования. Этот атрибут опционален, и по-умолчанию совпадает с полным именем класса, или
ролевым именем коллекции (qualified role name of the collection).
include Включать или нет все свойства. Опционально, и может принимать два возможных значения.
  • Значение all — включаются все свойства. Используется по-умолчанию.
  • Значение non-lazy включает только non-lazy свойства.

Пример 6.3. Конфигурация кэш-провайдеров с использованием файлов маппинга.

<cache
    usage="transactional"
    region="RegionName"
    include="all"
/>


Как и в Примере 6.2, “Конфигурация кэш-провайдеров с использованием аннотаций”, вы можете указывать атрибуты в файлах маппинга. Есть несколько специфических различий в синтаксисе атрибутов в файлах маппинга.

Имя Описание
usage Стратегия кэширования. Этот атрибут обязателен, и может принимать любое из следующих значений:
  • transactional
  • read-write
  • nonstrict-read-write
  • read-only

region Имя региона кэша второго уровня. По-умолчанию совпадает полным именем класса или ролевым именем коллекции.
include Могут ли свойства сущности, указанные с lazy=true, кэшироваться, когда разрешена
”ленивая” выборка на уровне атрибутов. По-умолчанию all и может быть также non-lazy


Вместо cache, вы можете использовать элементы class-cache, collection-cache в hibernate.cfg.xml.

6.2.2. Стратегии кэширования


  • read-only

    Read-only кэш хорош для данных, которые читаются, но не изменяются. Простой, хорошо работающий подход, безопасный к тому же в кластеризованном окружении.

  • nonstrict read-write

    Некоторым приложениям необходимо лишь иногда читать данные. Это тот случай, когда две транзакции врядли одновременно обновят одну и ту же сущность. В таком случае, вам не нужно ограничивать изоляцию транзакций, и nonstrict-read-write кэш весьма подходящ. Если кэш используется в JTA-окружении, вы должны указать hibernate.transaction.manager_lookup_class. В других окружениях, убедитесь что транзакция завершена, перед тем как вызвать Session.close() или Session.disconnect().

  • read-write

    Read-write кэш подходит для приложения, где данные обновляются регулярно. Не используйте read-write стратегию, если вам нужна сериализуемая изоляция транзакций. В JTA-окружении, укажите стратегию для получения JTA TransactionManager, выставив свойство hibernate.transaction.manager_lookup_class. В не-JTA окружениях, убедитесь, что транзакция завершена, перед тем как вызвать Session.close() или Session.disconnect().

    Важно

    Для использования read-write стратегии в кластеризованном окружении, нижележащая реализация кэша должна поддерживать блокировки. Встроенные кэш-провайдеры не поддерживают блокировки.


  • transactional

    Transactional стратегия кэширования предоставляет поддержку для транзакционных кэш-провайдеров, таких как JBoss TreeCache. Использовать такой кэш вы можете только в JTA-окружении, и для начала вам нужно будет указать hibernate.transaction.manager_lookup_class.


6.2.3. Провайдеры Hibernate для кэша второго уровня


Кэш Поддерживаемые стратегии
HashTable (только для тестов)
  • read-only
  • nonstrict read-write
  • read-write
EHCache
  • read-only
  • nonstrict read-write
  • read-write
  • transactional
Infinispan
  • read-only
  • transactional


6.3. Управление кэшем


6.3.1. Добавление/извлечение записей из кэша


Действия, добавляющие записи во внутренний кэш сессии:

Сохранение или апдейт сущности Извлечение сущности
  • save()
  • update()
  • saveOrUpdate()
  • load()
  • get()
  • list()
  • iterate()
  • scroll()


Синхронизация или удаление закэшированной записи. Состояние объекта синхронизируется с БД, когда вы вызываете метод flush(). Чтобы избежать синхронизации, вы можете удалить объект и его коллекции из кэша первого уровня с помощью метода evict(). Для удаления всех записей из кэша сессии, используйте метод Session.clear().

Пример 6.4. Удаление записи из кэша первого уровня

ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set
while ( cats.next() ) {
    Cat cat = (Cat) cats.get(0);
    doSomethingWithACat(cat);
    sess.evict(cat);
}


Как определить, принадлежит ли сущность кэшу сессии. Объект Session предоставляет метод contains() для определения принадлежности объекта к кэшу сессии.

Пример 6.5 Удаление из кэша второго уровня
Вы можете удалить закэшированное состояние сущности, всего класса, коллекции, или все роли коллекции, используя методы SessionFactory

sessionFactory.getCache().containsEntity(Cat.class, catId); // is this particular Cat currently in the cache

sessionFactory.getCache().evictEntity(Cat.class, catId); // evict a particular Cat

sessionFactory.getCache().evictEntityRegion(Cat.class);  // evict all Cats

sessionFactory.getCache().evictEntityRegions();  // evict all entity data

sessionFactory.getCache().containsCollection("Cat.kittens", catId); // is this particular collection currently in the cache

sessionFactory.getCache().evictCollection("Cat.kittens", catId); // evict a particular collection of kittens

sessionFactory.getCache().evictCollectionRegion("Cat.kittens"); // evict all kitten collections

sessionFactory.getCache().evictCollectionRegions(); // evict all collection data


.

6.1. Кэш запросов


Если у вас есть запросы, выполняющиеся снова и снова, с одними и теми же параметрами, кэширование запросов предоставит выигрыш в производительности.

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

Процедура 6.1. Разрешение кэширования запросов


  1. Выставьте свойство hibernate.cache.use_query_cache в true.

    Эта настройка создаст два новых региона кэширования:

    • org.hibernate.cache.internal.StandardQueryCache хранит результаты кэшированных запросов.
    • org.hibernate.cache.spi.UpdateTimestampsCache хранит временные метки недавних апдейтов в запрашиваемые таблицы. Эти временные метки валидируют результаты, берущиеся из кэша запроса (т.е через них проверяется актуальность объектов, прим.перев.)

  2. Настройте таймаут региона кэширования

    Если вы конфигурируете таймауты или окончание срока в вашей реализации кэша, выставьте таймаут нижележащего кэш-региона для UpdateTimestampsCache на большее значение, чем таймауты любого из кэшей запросов. Вполне возможно, и рекомендуется, выставлять бесконечный таймаут истечения у региона UpdateTimestampsCache. Если быть более конкретным, LRU-политика кэширования (Least Recently Used) не считается подходящей.

  3. Разрешите кэширование результатов для конкретных запросов.

    Так как большая часть запросов не выигрывает от кэширования, вам нужно разрешить кэширование только для индивидуальных запросов, даже после повсеместного разрешения кэширования. Для того, чтобы разрешить кэширование для определенного запроса, вызовите org.hibernate.Query.setCacheable(true). Вызов позволит запросу ”заглянуть” в кэш перед выполнением, или положить туда результаты после выполнения.


Кэш запросов не кэширует состояние актуальных сущностей в кэше. Он кэширует значения идентификаторов и результаты типов. Таким образом, всегда используете кэш запросов в паре с кэшем второго уровня для тех сущностей, которые должны кэшироваться как часть кэша запросов.

6.1.1. Регионы кэша запросов


Для лучшего контроля за политиками истечения актуальности у кэшей запросов, укажите именованный регион кэширования для определенного запроса, вызвав Query.setCacheRegion().

Пример 6.1. Метод setCacheRegion

List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
        .setEntity("blogger", blogger)
        .setMaxResults(15)
        .setCacheable(true)
        .setCacheRegion("frontpages")
        .list();


Чтобы заставить кэш запросов обновить один из его регионов, и проигнорировать любые закэшированные данные, вызовите org.hibernate.Query.setCacheMode(CacheMode.REFRESH). В паре с регионом, определенным для запроса, Hibernate выборочно обновит результаты, закэшированные в данном регионе. Это более эффективно, нежели групповое удаление записей (bulk eviction) из региона c помощью org.hibernate.SessionFactory.evictQueries().

6.2. Провайдеры кэша второго уровня


Hibernate совместим с некоторыми провайдерами кэша второго уровня. Ни один из провайдеров не поддерживает абсолютно все возможные стратегии кэширования, определенные в Hibernate. Секция 6.2.3, “Провайдеры кэша второго уровня для Hibernate” содержит список провайдеров, вместе с их интерфейсами и поддерживаемыми стратегиями кэширования. Для определений стратегий кэширования, см. Секцию 6.2.2, “Стратегии кэширования”.

6.2.1. Конфигурация пользовательских кэш-провайдеров


Вы можете конфигурировать ваши кэш-провайдеры, используя аннотации или файлы маппинга.
Сущности, по-умолчанию, не являются частью кэша второго уровня, и их использование не рекомендуется. Если вам все равно необходимо использовать сущности, установите элемент shared-cache-mode в persistence.xml, или используйте свойство javax.persistence.sharedCache.mode в вашей конфигурации. Используйте одно из значений Таблицы 6.1, “Возможные значения для Shared Cache Mode”.

Таблица 6.1. Возможные значения для Shared Cache Mode

Значение Описание
ENABLE_SELECTIVE Сущности не будут кэшироваться, до тех пор, пока вы явно не пометите их как кэшируемые.
Это значение рекомендуется и используется по-умолчанию.
DISABLE_SELECTIVE Сущности будут кэшироваться, до тех пор, пока вы явно не отмените кэширование на них.
ALL Все сущности будут кэшироваться, в независимости от того, пометите ли вы их как некэшируемые.
NONE Ни одна сущность не будет кэшироваться, даже если вы пометите их как кэшируемые. Эта опция отменяет
работу кэша второго уровня.


Глобальная concurrency-стратегия устанавливается с помощью свойства hibernate.cache.default_cache_concurrency_strategy. См. возможные значения в секции 6.2.2, “Стратегии кэширования”.

Важно

Когда это возможно, определяйте concurrency-стратегию кэширования только для определенной сущности, но не глобально. Используйте аннотацию @org.hibernate.annotations.Cache.

Пример 6.2. Конфигурация кэш-провайдеров с использованием аннотаций.

@Entity 
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Forest { ... }


Вы можете кэшировать содержимое коллекций или идентификаторов, если коллекция содержит другие сущности. Используйте аннотацию @Cache на свойстве коллекции.

@Cache содержит несколько атрибутов.

Атрибуты аннотации @Cache

Имя Описание
usage Заданная concurrency-стратегия кэша, которая может быть:
  • NONE
  • READ_ONLY
  • NONSTRICT_READ_WRITE
  • READ_WRITE
  • TRANSACTIONAL

region Регион кэширования. Этот атрибут опционален, и по-умолчанию совпадает с полным именем класса, или
ролевым именем коллекции (qualified role name of the collection).
include Включать или нет все свойства. Опционально, и может принимать два возможных значения.
  • Значение all — включаются все свойства. Используется по-умолчанию.
  • Значение non-lazy включает только non-lazy свойства.

Пример 6.3. Конфигурация кэш-провайдеров с использованием файлов маппинга.

<cache
    usage="transactional"
    region="RegionName"
    include="all"
/>


Как и в Примере 6.2, “Конфигурация кэш-провайдеров с использованием аннотаций”, вы можете указывать атрибуты в файлах маппинга. Есть несколько специфических различий в синтаксисе атрибутов в файлах маппинга.

Имя Описание
usage Стратегия кэширования. Этот атрибут обязателен, и может принимать любое из следующих значений:
  • transactional
  • read-write
  • nonstrict-read-write
  • read-only

region Имя региона кэша второго уровня. По-умолчанию совпадает полным именем класса или ролевым именем коллекции.
include Могут ли свойства сущности, указанные с lazy=true, кэшироваться, когда разрешена
”ленивая” выборка на уровне атрибутов. По-умолчанию all и может быть также non-lazy


Вместо cache, вы можете использовать элементы class-cache, collection-cache в hibernate.cfg.xml.

6.2.2. Стратегии кэширования


  • read-only

    Read-only кэш хорош для данных, которые читаются, но не изменяются. Простой, хорошо работающий подход, безопасный к тому же в кластеризованном окружении.

  • nonstrict read-write

    Некоторым приложениям необходимо лишь иногда читать данные. Это тот случай, когда две транзакции врядли одновременно обновят одну и ту же сущность. В таком случае, вам не нужно ограничивать изоляцию транзакций, и nonstrict-read-write кэш весьма подходящ. Если кэш используется в JTA-окружении, вы должны указать hibernate.transaction.manager_lookup_class. В других окружениях, убедитесь что транзакция завершена, перед тем как вызвать Session.close() или Session.disconnect().

  • read-write

    Read-write кэш подходит для приложения, где данные обновляются регулярно. Не используйте read-write стратегию, если вам нужна сериализуемая изоляция транзакций. В JTA-окружении, укажите стратегию для получения JTA TransactionManager, выставив свойство hibernate.transaction.manager_lookup_class. В не-JTA окружениях, убедитесь, что транзакция завершена, перед тем как вызвать Session.close() или Session.disconnect().

    Важно

    Для использования read-write стратегии в кластеризованном окружении, нижележащая реализация кэша должна поддерживать блокировки. Встроенные кэш-провайдеры не поддерживают блокировок.


  • transactional
    Transactional стратегия кэширования предоставляет поддержку для транзакционных кэш-провайдеров, таких как JBoss TreeCache. Использовать такой кэш вы можете только в JTA-окружении, и для начала вам нужно будет указать hibernate.transaction.manager_lookup_class.


6.2.3. Провайдеры Hibernate для кэша второго уровня


Кэш Поддерживаемые стратегии
HashTable (только для тестов)
  • read-only
  • nonstrict read-write
  • read-write
EHCache
  • read-only
  • nonstrict read-write
  • read-write
  • transactional
Infinispan
  • read-only
  • transactional


6.3. Управление кэшем


6.3.1. Добавление/извлечение записей из кэша


Действия, добавляющие записи во внутренний кэш сессии:

Сохранение или апдейт сущности Извлечение сущности
  • save()
  • update()
  • saveOrUpdate()
  • load()
  • get()
  • list()
  • iterate()
  • scroll()


Синхронизация или удаление закэшированной записи. Состояние объекта синхронизируется с БД, когда вы вызываете метод flush(). Чтобы избежать синхронизации, вы можете удалить объект и его коллекции из кэша первого уровня с помощью метода evict(). Для удаления всех записей из кэша сессии, используйте метод Session.clear().

Пример 6.4. Удаление записи из кэша первого уровня

ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set
while ( cats.next() ) {
    Cat cat = (Cat) cats.get(0);
    doSomethingWithACat(cat);
    sess.evict(cat);
}


Как определить, принадлежит ли сущность кэшу сессии. Объект Session предоставляет метод contains() для определения принадлежности объекта к кэшу сессии.

Пример 6.5 Удаление из кэша второго уровня
Вы можете удалить закэшированное состояние сущности, всего класса, коллекции, или все роли коллекции, используя методы SessionFactory

sessionFactory.getCache().containsEntity(Cat.class, catId); // is this particular Cat currently in the cache

sessionFactory.getCache().evictEntity(Cat.class, catId); // evict a particular Cat

sessionFactory.getCache().evictEntityRegion(Cat.class);  // evict all Cats

sessionFactory.getCache().evictEntityRegions();  // evict all entity data

sessionFactory.getCache().containsCollection("Cat.kittens", catId); // is this particular collection currently in the cache

sessionFactory.getCache().evictCollection("Cat.kittens", catId); // evict a particular collection of kittens

sessionFactory.getCache().evictCollectionRegion("Cat.kittens"); // evict all kitten collections

sessionFactory.getCache().evictCollectionRegions(); // evict all collection data


6.3.1.1. Взаимодействии сессии Session и кэша второго уровня

Имя Описание
CacheMode.NORMAL Чтение сущности из и запись ее в кэш второго уровня.
CacheMode.GET Чтение сущностей из кэша второго уровня, но без записи в кэш, кроме обновления данных
CacheMode.PUT Обходит эффект hibernate.cache.use_minimal_puts и принудительно
делает обновление всех загруженных из бд сущностей.

6.3.1.2. Просмотр содержимого кэша второго уровня и кэшей запросов


После разрешения статистики, вы можете просматривать содержимое кэша второго уровня или региона кэширования.

Процедура 6.2. Разрешение статистики

  1. Выставьте hibernate.generate_statistics в true.
  2. Необязательно – выставьте hibernate.cache.use_structured_entries в true, чтобы Hibernate мог хранить записи в понятном пользователю формате.

Пример 6.6. Просмотр кэша второго уровня через Statistics API

Map cacheEntries = sessionFactory.getStatistics()
        .getSecondLevelCacheStatistics(regionName)
        .getEntries();

© Habrahabr.ru