HibernateSeniorTechnical
Что такое метод flush() в Hibernate и когда сессия сбрасывается автоматически?
flush() синхронизирует изменения persistence context с БД, генерируя SQL INSERT/UPDATE/DELETE, но не делает COMMIT. Автоматический flush срабатывает перед выполнением JPQL/HQL-запроса (FlushMode.AUTO) и перед commit транзакции.
flush() в Hibernate
session.flush() записывает накопленные изменения из первого уровня кэша (persistence context) в БД в виде SQL-операций. Важно: flush — это не commit. Данные попадают в текущую транзакцию, но не становятся видимыми другим транзакциям до вызова tx.commit().
Что происходит при flush
- Dirty checking — Hibernate сравнивает текущее состояние каждого managed-объекта со snapshot, сделанным при его загрузке.
- Генерация SQL — для изменённых объектов формируются UPDATE, для новых — INSERT, для удалённых — DELETE.
- Сортировка операций — по умолчанию Hibernate сортирует SQL согласно ActionQueue: сначала INSERT, затем UPDATE, затем DELETE (с учётом foreign key порядка).
- Выполнение через JDBC — SQL отправляется в БД в рамках текущей транзакции.
FlushMode — когда flush происходит автоматически
// FlushMode.AUTO (умолчание в Session)
// flush срабатывает:
// 1. Перед выполнением HQL/JPQL-запроса, если запрос может затронуть изменённые данные
// 2. Перед commit транзакции
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
User user = session.get(User.class, 1L);
user.setEmail("new@example.com"); // dirty — изменение зафиксировано в контексте
// Следующий запрос затрагивает таблицу users → AUTO flush сработает ДО запроса
List<User> users = session.createQuery("from User where active = true", User.class)
.list(); // UPDATE user SET email=? выполнится перед этим SELECT
tx.commit(); // ещё один flush + commit
session.close();
Режимы FlushMode
// FlushMode.MANUAL — flush только явным вызовом session.flush()
session.setHibernateFlushMode(FlushMode.MANUAL);
User user = session.get(User.class, 1L);
user.setName("Test");
// Запрос НЕ вызовет flush автоматически
List<User> all = session.createQuery("from User", User.class).list();
// user.getName() в all может быть старым! Stale read внутри транзакции.
session.flush(); // явный flush
// FlushMode.COMMIT — flush только перед commit
session.setHibernateFlushMode(FlushMode.COMMIT);
// FlushMode.ALWAYS — flush перед каждым запросом (дорого, но предсказуемо)
session.setHibernateFlushMode(FlushMode.ALWAYS);
Явный flush и ConstraintViolationException
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
try {
Product product = new Product();
product.setSku("DUPLICATE-SKU"); // нарушает UNIQUE constraint
session.persist(product);
// Явный flush позволяет поймать ConstraintViolationException ДО commit
// и принять решение (rollback, логирование) в этом же блоке
session.flush();
tx.commit();
} catch (ConstraintViolationException e) {
tx.rollback();
log.error("SKU уже существует: {}", e.getMessage());
}
flush() в Spring @Transactional
В Spring при использовании EntityManager (JPA) flush вызывается автоматически перед коммитом транзакции. Явный entityManager.flush() нужен для получения сгенерированного ID до закрытия транзакции или для принудительной проверки constraint в batch-операциях.
@Transactional
public Long createUser(CreateUserDto dto) {
User user = new User(dto.email());
entityManager.persist(user);
entityManager.flush(); // Hibernate выполнит INSERT и заполнит user.getId()
return user.getId(); // ID доступен до commit транзакции
}
Подводные камни
FlushMode.AUTOанализирует, пересекается ли запрос с «dirty» таблицами, но эвристика неточна — иногда flush не срабатывает перед нативным SQL-запросом (createNativeQuery), что приводит к stale read.- При
FlushMode.MANUALJPQL-запросы будут возвращать данные, не учитывающие изменения в текущей сессии — это тонкий источник багов в batch-операциях. - Batch insert/update: каждый
session.flush() + session.clear()должен вызываться с интервалом, равнымhibernate.jdbc.batch_size, иначе первый уровень кэша вырастет до OOM. ConstraintViolationExceptionпри flush делает транзакцию unusable — даже если поймать исключение, нужен rollback; попытка продолжить работу с той же Session приведёт к непредсказуемому поведению.- Порядок SQL в ActionQueue может не совпадать с порядком вызовов в коде — если foreign key constraint требует строгого порядка INSERT, используйте
session.flush()между операциями явно. - В Hibernate 6 изменилась логика dirty checking: используется bytecode enhancement вместо reflection по умолчанию — это быстрее, но требует корректной конфигурации плагина при сборке.
- Открытый в Spring
EntityManagerв рамках Open-Session-In-View автоматически вызывает flush в конце HTTP-запроса — изменения, сделанные в сервисном слое, могут неожиданно закоммититься даже без явного@Transactionalна контроллере. - Вызов
flush()на read-only сессии (session.setDefaultReadOnly(true)) не генерирует SQL — это не ошибка, но может вводить в заблуждение при отладке.
Common mistakes
- Путать термин «flush» с соседним механизмом Hibernate.
- Не называть границу lifecycle, transaction, thread или request для «flush».
- Игнорировать production-эффекты «flush»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «flush» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «flush».
- Уточнить, какие настройки или API меняют «flush» в реальном сервисе.