Стратегии параллельного доступа

После того, как ты включаешь в 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();
undefined
1
Задача
Модуль 4. Работа с БД, 14 уровень, 5 лекция
Недоступна
task1405
Есть два класса-энтити: Employee и Task. Добавь им аннотации, чтобы для них был задействован кеш второго уровня...