На днях столкнулся с задачей кэширования запросов к БД. Приложение использовало, как это не странно, hibernate. В этой статье я расскажу, о том какие бывают кэши в hibernate, каким образом их использовать и как настраивать.

Начнем по порядку.
Всего в hibernate существует три вида кэша:

  1. сессионый,
  2. кэш sessionFactory или же кеш второго уровня и
  3. кэш запросов

Сессионый кэш, исходя из названия, используются в рамках одной сессии. Для каждой сессии свой кэш, как только сессия закрывается — он очищается. Для ясности, приведу пример :

Session session = HibernateUtil.getSessionFactory().openSession();
try {
  Criteria criteria = session.createCriteria(Account.class);
  criteria.add(Restrictions.eq("id", id));
  Account firstAccount = (Account) criteria.uniqueResult();
  Account secondAccount = (Account) criteria.uniqueResult();
} finally {
  session.close();
}

В данном случае произойдет всего лишь одно обращение к БД, secondAccount уже возьмется из кэша. В hibernate данный тип кэша включен по умолчанию и для его работы делать ничего не нужно, в отличии от остальных. У hibernate отсутствуют реализации кэша второго уровня и кэша запросов. Чтобы их использовать потребуется подключить сторонние библиотеки.

Кэш второго уровня или sessionFactory кэш распространяется на все сессии. По умолчанию, выключен. Чтобы включить нужно добавить в зависимости библиотеку предоставляющую его реализацию. Я использовал ehcache. Оговорюсь сразу, что все настройки применимы для hibernate версии 4.0 и выше.

pom.xml

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-core</artifactId>
  <version>${hibernate.version}</version>
</dependency>
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-ehcache</artifactId>
  <version>${hibernate.version}</version>
</dependency>
<dependency>
  <groupId>net.sf.ehcache</groupId>
  <artifactId>ehcache-core</artifactId>
  <version>2.6.11</version>
</dependency>

И добавить в конфигурацию hibernate следующие:

<property name="hibernate.cache.use_second_level_cache">
  true
</property>
<property name="hibernate.cache.region.factory_class">
  org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
</property>
<property name="net.sf.ehcache.configurationResourceName">
  ehcache.xml
</property>

Параметры для кэшей в Ehcache задаются в отдельном файле echache.xml

<ehcache>
  <diskStore path="java.io.tmpdir"/>
  <defaultCache
            maxElementsInMemory="100000"
            eternal="false"
            statistics="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            diskPersistent="false"
            diskSpoolBufferSizeMB="500"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"/>
 </ehcache>

Флаг eternal, показывает, хотим ли мы переопределить параметры timeToIdleSeconds, timeToLiveSeconds. Если выставлен true, то значения игнорируются и объекты в кэше никогда не удаляются. Стоит обратить внимание на параметр path в diskStore, он указывает путь до директории, где будут храниться файлы кэша. Значение java.io.tmpdir — путь до стандартной папки с временными файлами без привязки к операционной системе.

Почти все, теперь осталось только пометить те классы, объекты которых будет кэшироваться. Если маппинг определен при помощи xml, то указывается тег:

<cache usage="read-write"/> 

Если аннотациями, то следует добавить рядом с @Entity следующее: @Cache(usage = CacheConcurrencyStrategy.READ_WRITE).

Следует уточнить еще один момент, в качестве значений может быть следующие:

  1. read-write в зависимости от уровня транзакции, обновление объектов происходит согласно правилам read committed или же repeatable read. Данная стратегия является компромиссом при многопоточном доступе и частом обновлении информации.
  2. nonstrict-read-write применимо для объектов, которые подвержены изменениям, происходит это без блокировки кеша, при возможности предоставляется доступ к объекту из разных потоков, но тогда при этом не гарантируется что будет получена самая последняя версия объекта
  3. read-only используется для объектов, которые никогда не изменяются

Чтобы включить кэширование для запросов, нужно так же добавить зависимости, указать параметры в файле echache.xml и разрешить кэширование запросов в hibernate.properties:

<property name="hibernate.cache.use_query_cache">true</property> 

Теперь, для тех запросов, результаты которых должны попадать в кэш, нужно вызвать метод setCacheable(true), если используется Criteria или запросы HQL.

Photo by Patrick Lindenberg on Unsplash