На днях столкнулся с задачей кэширования запросов к БД. Приложение использовало, как это не странно, hibernate. В этой статье я расскажу, о том какие бывают кэши в hibernate, каким образом их использовать и как настраивать.
Начнем по порядку.
Всего в hibernate существует три вида кэша:
- сессионый,
- кэш sessionFactory или же кеш второго уровня и
- кэш запросов
Сессионый кэш, исходя из названия, используются в рамках одной сессии. Для каждой сессии свой кэш, как только сессия закрывается — он очищается. Для ясности, приведу пример :
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).
Следует уточнить еще один момент, в качестве значений может быть следующие:
- read-write в зависимости от уровня транзакции, обновление объектов происходит согласно правилам read committed или же repeatable read. Данная стратегия является компромиссом при многопоточном доступе и частом обновлении информации.
- nonstrict-read-write применимо для объектов, которые подвержены изменениям, происходит это без блокировки кеша, при возможности предоставляется доступ к объекту из разных потоков, но тогда при этом не гарантируется что будет получена самая последняя версия объекта
- 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