Стратегии параллельного доступа
После того, как ты включаешь в Hibernate кеширование второго уровня, тебе нужно объяснить Hibernate, какие Entity-объекты мы хотим кешировать и как.
Для этого у Hibernate есть специальная аннотация для Entity-классов — @Cache. Пример:
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
Эту аннотацию нужно писать у каждой Entity-сущности, для которой мы хотим задействовать кэш второго уровня. Пример:
@Entity
@Table(name = "employee")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Employee {
@Id
private Integer id;
private Set<Task> tasks;
}
У Hibernate есть 4 возможных стратегии доступа для кэшируемой сущности, если к ней обращаются из разных потоков:
- read-only
- read-write
- nonstrict-read-write
- transactional
Только для чтения (read-only). Стратегия параллелизма, подходящая для данных, которая никогда не изменяется. Hibernate будет просто хранить эти объекты у себя в памяти. Используй его только для справочных данных.
В базах данных хранится очень много информации, которая никогда не меняется. Например, таблица хранит список событий, которые только добавляются, но никогда не изменяются и не удаляются. Если тебе нужно работать с этой таблицей через Hibernate, то тебе подойдет стратегия кэширования read-only.
Чтение-запись (read-write). Используй эту стратегию для данных, предназначенных главным образом для чтения. Однако Hibernate будет отслеживать попытки изменения этих данных, хотя и рассчитывает, что они будут очень редкими.
Кешировать нужно в основном те объекты, которые редко меняются и часто читаются/запрашиваются. Если у тебя есть такие объекты, то для них нужно использовать стратегию read-write.
Nonstrict-read-write. Эта стратегия не гарантирует согласованности между кэшем и базой данных. Используй эту стратегию, если данные почти никогда не изменяются и небольшая вероятность устаревших данных не является критической проблемой.
В отличии от стратегии read-write, эта стратегия подразумевает, что изменяемые данные не лочатся на чтение. Это может привести к тому, что объект изменился в одном месте, а в другом кто-то читает его старую версию.
Например, пользователь изменил свой комментарий, но остальные пользователи еще какое-то время видят его старую версию. Если для тебя это не является проблемой, тогда используй стратегию nonstrict-read-write.
Транзакционный. Используй эту стратегию для данных, предназначенных главным образом для чтения, где важно предотвратить устаревшие данные в параллельных транзакциях в редких случаях обновления.
Хранение данных в кэше
Еще одна важная деталь про кэш второго уровня, про которую стоило бы вспомнить, — Hibernate не хранит сами объекты твоих классов. Он хранит информацию в виде массивов строк, чисел и т. д.
И идентификатор объекта выступает указателем на эту информацию. Концептуально это нечто вроде Map, в которой id объекта — ключ, а массивы данных — значение. Можно представить себе это так:
1 -> { "Иванов", 1, null , {1,2,5} }
2 -> { "Петров", 2, null , {1,2,5} }
3 -> { "Сидоров", 3, null , {1,2,5} }
Что есть очень разумно, учитывая, сколько лишней памяти занимает каждый объект.
Помимо вышесказанного, следует помнить — зависимости твоего Entity-класса по умолчанию также не кэшируются. Например, если рассмотреть класс выше, Employee, то при выборке коллекция tasks будет доставаться из БД, а не из кэша второго уровня.
Если ты хочешь также кешировать и зависимости, то класс должен выглядеть так:
@Entity
@Table(name = "employee")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Employee {
@Id
private Integer id;
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Task> tasks;
}
И последняя деталь — чтение из кэша второго уровня происходит только в том случае, если нужный объект не был найден в кэше первого уровня.
CacheMode
Hibernate позволяет очень гибко управлять кешированием. Ты можешь настроить режим кеширования для каждой отдельной сессии или даже для каждого запроса к базе данных.
Всего таких режимов пять:
- GET
- IGNORE
- NORMAL
- PUT
- REFRESH
В таблице ниже описана их работа:
CacheMode | Описание |
---|---|
GET | Данные читаются из кэша, но не добавляются в него. |
IGNORE | Сессия не взаимодействует с кэшем. |
NORMAL | Данные читаются из кэша и добавляются в него. |
PUT | Данные никогда не берутся из кэша, но добавляются в него. |
REFRESH | Данные никогда не берутся из кэша, но добавляются в него. В этом режиме дополнительно используется настройка hibernate.cache.use_minimal_puts. |
Пример установки режима кэширования для сессии:
session.setCacheMode(CacheMode.GET);
Employee director = session.createQuery("from Employee where id = 4").uniqueResult();
А также пример установки режима для сессии и запроса:
session.setCacheMode(CacheMode.GET);
Query query = session.createQuery("from Employee where id = 4");
query.setCacheMode(CacheMode.IGNORE); // Игнорирует работу с кэшем для этого запроса
Employee director = query.uniqueResult();
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ