Питання 12 · Розділ 16

Що таке dirty checking в Hibernate

Dirty checking — механізм автоматичного відстеження змін сутностей в persistence context та їх збереження в базу даних. Це одна з ключових можливостей Hibernate, яка позбавляє р...

Мовні версії: English Russian Ukrainian

Огляд

Dirty checking — механізм автоматичного відстеження змін сутностей в persistence context та їх збереження в базу даних. Це одна з ключових можливостей Hibernate, яка позбавляє розробника від ручного написання UPDATE-запитів.


🟢 Junior Level

Що таке dirty checking

Dirty checking — Hibernate автоматично відстежує зміни у сутностей в persistence context і при збереженні автоматично генерує UPDATE-запит.

@Transactional
public void updateUser(Long id, String name) {
    // 1. Завантаження з БД — сутність стає Persistent
    User user = entityManager.find(User.class, id);

    // 2. Зміна поля — Hibernate "помічає" зміну
    user.setName(name);

    // 3. НЕ ПОТРІБЕН em.merge() чи em.save()!
    // При commit — Hibernate автоматично зробить UPDATE
}

Як це працює — просто

1. Завантаження: Hibernate завантажує entity і зберігає його "знімок" (snapshot)
2. Зміна: ви змінюєте поле в об'єкті
3. Flush: Hibernate порівнює snapshot з поточним станом
4. Якщо різні → генерує UPDATE SQL
5. Якщо однакові → нічого не робить

🟡 Middle Level

Детальний механізм dirty checking

Крок 1: Завантаження entity
- entityManager.find(User.class, 1L)
- Hibernate створює EntityEntry з snapshot
- snapshot = копія всіх полів на момент завантаження

Крок 2: Зміна
- user.setName("New Name")
- Hibernate НЕ робить нічого одразу

Крок 3: Flush (при commit або явно)
- Hibernate обходить всі entities в persistence context
- Для кожного: порівнює snapshot з поточним станом
- Якщо є зміни → schedule UPDATE
- Виконує всі UPDATE в БД
- Оновлює snapshot

Коли відбувається flush

// 1. При commit транзакції (автоматично)
@Transactional
public void update() {
    user.setName("New");
}  // commit → flush → UPDATE

// 2. При entityManager.flush() (явно)
entityManager.flush();  // UPDATE виконано

// 3. Перед виконанням запиту (щоб повернути актуальні дані)
user.setName("New");
List<User> users = entityManager.createQuery("FROM User", User.class)
    .getResultList();  // перед цим — flush!

Оптимізація — read-only сутності

// Якщо сутність не буде змінюватися — позначити як read-only
User user = entityManager.createQuery("FROM User u WHERE u.id = :id", User.class)
    .setParameter("id", id)
    .setHint("org.hibernate.readOnly", true)
    .getSingleResult();

// Переваги:
// 1. Не створюється snapshot (економія пам'яті)
// 2. Dirty checking не виконується
// 3. Менше overhead на flush

🔴 Senior Level

Внутрішня реалізація

PersistenceContext:

StatefulPersistenceContext {
    entityEntries: Map<Object, EntityEntry>
}

EntityEntry {
    loadedState: Object[]    // snapshot при завантаженні
    state: Object[]          // поточний стан
    status: Status          // MANAGED, DELETED, READ_ONLY
    id: Serializable
    version: Object
}

Алгоритм flush:
1. Для кожного entity в entityEntries:
   if entity.status == MANAGED:
     if !Arrays.equals(entry.loadedState, entry.state):
       scheduleUpdate(entity)
2. Виконати всі scheduled UPDATE
3. Оновити loadedState = state

Performance характеристики

Dirty checking overhead:
- O(N) де N = число entities в persistence context
- Для кожного entity: array comparison всіх полів
- Для 10k+ entities — може бути помітно повільно

Оптимізації:
- read-only hint → O(0) (пропускає entity)
- clear() періодично → зменшує N
- StatelessSession → без dirty checking

Best Practices

✅ Dirty checking для простих оновлень
✅ Read-only hint для запитів без змін
✅ entityManager.clear() для великих операцій
✅ Розуміння коли відбувається flush
✅ Періодичний flush + clear в batch операціях

❌ Dirty checking для великих persistence context
❌ Без read-only hint коли не потрібне оновлення
❌ Ігнорування performance impact

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • Dirty checking — Hibernate автоматично відстежує зміни managed-сутностей
  • При завантаженні зберігається snapshot, при flush порівнюється з поточним станом
  • Не потрібен explicit merge() чи save() для оновлень managed-сутностей
  • O(N) overhead де N = число entities в persistence context
  • Flush відбувається при: commit, entityManager.flush(), перед запитом
  • Read-only hint економить пам’ять — не створюється snapshot, dirty checking пропускається

Пов’язані теми:

  • [[7. Опишіть життєвий цикл Entity в Hibernate]]
  • [[13. Як працює механізм flush в Hibernate]]
  • [[9. Що таке кеш першого рівня в Hibernate]]
  • [[14. У чому різниця між persist() і merge()]]