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

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