1. Стратегії паралельного доступу

Після того, як ти включаєш у 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.

Транзакційний. Використовуй цю стратегію для даних, призначених головним чином для читання, де важливо запобігти застарілим даним в паралельних транзакціях в окремих випадках оновлення.

2. Зберігання даних у кеші

Ще одна важлива деталь про кеш другого рівня, яку варто було б згадати, — 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;
}

І остання деталь: читання з кешу другого рівня відбувається лише в тому випадку, якщо потрібний об'єкт не було знайдено у кеші першого рівня.

3. 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();