Вопрос 13 · Раздел 16

Как работает механизм flush в Hibernate

Flush — процесс синхронизации данных из persistence context (кэш Hibernate) в базу данных. Понимание механизма flush критически важно для управления транзакциями и оптимизации п...

Версии по языкам: English Russian Ukrainian

Обзор

Flush — процесс синхронизации данных из persistence context (кэш Hibernate) в базу данных. Понимание механизма flush критически важно для управления транзакциями и оптимизации производительности.


🟢 Junior Level

Что такое flush

Flush — это процесс записи всех изменений из памяти (persistence context) в базу данных.

User user = entityManager.find(User.class, 1L);
user.setName("New Name");  // изменено в памяти

// Flush ещё не произошёл — БД НЕ обновлена!

entityManager.flush();  // явный flush — UPDATE выполнен в БД
// или
// При commit — автоматический flush

Когда происходит flush

  1. При commit транзакции — автоматически
  2. При entityManager.flush() — явно
  3. Перед выполнением запроса — чтобы вернуть актуальные данные
@Transactional
public void updateUser() {
    User user = entityManager.find(User.class, 1L);
    user.setName("New");

    // flush ещё не произошёл
    // БД всё ещё содержит старое имя

    // При выходе из метода → @Transactional commit → flush
}

Пример ручного flush

@Transactional
public void batchInsert(List<User> users) {
    for (int i = 0; i < users.size(); i++) {
        entityManager.persist(users.get(i));

        if (i % 50 == 0) {
            entityManager.flush();  // сбросить в БД
            entityManager.clear();  // очистить кэш
        }
    }
}

🟡 Middle Level

FlushMode

// AUTO (по умолчанию) — flush при необходимости
entityManager.setFlushMode(FlushModeType.AUTO);

// COMMIT — flush только при commit
entityManager.setFlushMode(FlushModeType.COMMIT);

// ALWAYS — перед каждым запросом (редко используется)
entityManager.setFlushMode(FlushModeType.ALWAYS);
// (Примечание: FlushModeType.ALWAYS устарел в Hibernate 5 и удалён в Hibernate 6. В современных версиях используйте AUTO или COMMIT.)

FlushMode.AUTO vs COMMIT

// AUTO — flush перед запросом
User user = entityManager.find(User.class, 1L);
user.setName("New");

// Перед этим запросом — flush (чтобы вернуть актуальные данные)
List<User> users = entityManager.createQuery("FROM User", User.class)
    .getResultList();  // включает "New" user

// COMMIT — flush только при commit
entityManager.setFlushMode(FlushModeType.COMMIT);
user.setName("New");

// Этот запрос НЕ увидит "New" user (flush ещё не был)
List<User> users = entityManager.createQuery("FROM User", User.class)
    .getResultList();  // НЕ включает "New" user

Ручной flush для батч-операций

@Transactional
public void batchUpdate(List<User> users) {
    for (int i = 0; i < users.size(); i++) {
        entityManager.merge(users.get(i));

        if (i % 50 == 0) {
            entityManager.flush();  // сбросить 50 UPDATE в БД
            entityManager.clear();  // очистить persistence context
        }
    }
    // Финальный flush для оставшихся
    entityManager.flush();
    entityManager.clear();
}

Типичные ошибки

// ❌ Частый flush — медленно
for (User user : users) {
    entityManager.persist(user);
    entityManager.flush();  // ❌ каждый раз — очень медленно!
}

// ✅ Периодический flush
for (int i = 0; i < users.size(); i++) {
    entityManager.persist(users.get(i));
    if (i % 50 == 0) {
        entityManager.flush();
        entityManager.clear();
    }
}

🔴 Senior Level

Внутренняя реализация

Процесс flush:

1. Dirty checking
   - Обход всех entities в persistence context
   - Сравнение snapshot с текущим состоянием
   - Для изменённых — schedule UPDATE

2. Generation order
   - INSERT сначала (чтобы получить ID)
   - UPDATE затем
   - DELETE в конце (чтобы избежать FK violations)

3. SQL generation
   - Для каждого scheduled operation
   - Генерирует SQL с учётом dirty fields

4. Execution
   - Выполняет SQL в БД (JDBC batch)
   - Обновляет snapshot

5. Post-flush
   - Обновляет EntityEntry состояния
   - Сбрасывает dirty flags

Order of operations

При flush Hibernate соблюдает порядок:

1. Все entity INSERT (в порядке persist())
2. Все entity UPDATE (в порядке dirty detection)
3. Все collection UPDATE (для изменений коллекций)
4. Все collection DELETE (для удалённых элементов)
5. Все entity DELETE (в порядке remove())

Это важно для:
- Foreign key constraints
- Referential integrity
- Identity generation

Этот порядок критичен из-за foreign key constraints. Если Hibernate
попытается сделать INSERT child до INSERT parent, БД выбросит FK violation.
Аналогично — DELETE parent до DELETE child.

Flush и оптимистичная блокировка

@Version
private Integer version;

// При flush:
// UPDATE users SET name = ?, version = version + 1
// WHERE id = ? AND version = ?

// Если version не совпал → OptimisticLockException

Продвинутые настройки

spring:
  jpa:
    properties:
      hibernate:
        # Порядок flush для разных операций
        order_inserts: true       # группировка INSERT
        order_updates: true       # группировка UPDATE
        order_deletes: true       # группировка DELETE

        # JDBC batch size
        jdbc.batch_size: 50

Flush и транзакции

// @Transactional определяет границы flush
@Transactional
public void method1() {
    user.setName("A");
    // flush при commit
}

@Transactional
public void method2() {
    user.setName("B");
    entityManager.flush();  // явный flush
    // ещё один flush при commit (если были изменения)
}

Best Practices

✅ AUTO flush mode по умолчанию
✅ Ручной flush для батч-операций
✅ flush + clear каждые 50-100 entities
✅ COMMIT mode когда не нужны актуальные данные
✅ order_inserts/order_updates для оптимизации

❌ ALWAYS mode (избегайте)
❌ Flush без причины
❌ Без clear после flush в батчах
❌ Игнорирование порядка операций

🎯 Шпаргалка для интервью

Обязательно знать:

  • Flush — синхронизация данных из persistence context в БД
  • Происходит при: commit, entityManager.flush(), перед запросом (AUTO mode)
  • FlushMode: AUTO (по умолчанию), COMMIT (только при commit), ALWAYS (устарел)
  • Порядок операций: INSERT → UPDATE → DELETE (для FK constraints)
  • Для batch: flush + clear каждые 50-100 entities
  • order_inserts/order_updates — группировка SQL для оптимизации

Частые уточняющие вопросы:

  • AUTO vs COMMIT? AUTO — flush перед запросом (актуальные данные), COMMIT — только при commit
  • Почему порядок INSERT→UPDATE→DELETE важен? Foreign key constraints: нельзя INSERT child до INSERT parent
  • Почему частый flush медленный? Каждый flush = SQL запросы, при 1000 entities × flush = 1000 SQL
  • Что такое JDBC batch_size? Группирует SQL запросы для отправки пакетами, уменьшает network round trips

Красные флаги (НЕ говорить):

  • «Flush после каждого persist» — очень медленно, лучше пакетный flush
  • «ALWAYS mode для всех запросов» — устарел, огромный overhead
  • «Без clear после flush в батчах» — persistence context растёт, OOM
  • «Не понимаю порядок операций» — может вызвать FK violation

Связанные темы:

  • [[12. Что такое dirty checking в Hibernate]]
  • [[7. Опишите жизненный цикл Entity в Hibernate]]
  • [[14. В чём разница между persist() и merge()]]
  • [[17. Как реализовать оптимистичную блокировку в JPA]]