Что делает метод refresh()
Метод refresh() интерфейса EntityManager принудительно перезагружает состояние управляемой сущности (Managed) напрямую из реляционной базы данных. Он синхронизирует поля Java-об...
Метод refresh() интерфейса EntityManager принудительно перезагружает состояние управляемой сущности (Managed) напрямую из реляционной базы данных. Он синхронизирует поля Java-объекта в оперативной памяти JVM со строкой таблицы СУБД, безвозвратно перезаписывая любые локальные несохраненные изменения и обновляя снимок состояния в кэше первого уровня L1.
🟢 Junior Level
Назначение и базовое поведение refresh()
По умолчанию Hibernate полагается на кэш первого уровня: если сущность уже загружена в сессию, повторный вызов find() не обращается к БД. Метод refresh() игнорирует кэш L1, принудительно выполняет физический SQL SELECT по первичному ключу и затирает текущие поля объекта актуальными данными из базы:
@Transactional
public void refreshDemo() {
User user = entityManager.find(User.class, 1L); // user.getName() == "Иван"
// 1. Изменяем поле в оперативной памяти
user.setName("Временное имя");
// 2. Вызываем refresh()
entityManager.refresh(user);
// Hibernate выполняет: SELECT * FROM users WHERE id = 1;
// 3. Локальные изменения стерты!
System.out.println(user.getName()); // Снова выведет "Иван"!
// При коммите транзакции SQL UPDATE сгенерирован НЕ БУДЕТ.
}
Главные правила вызова refresh()
- Работает только для сущностей со статусом
Managed: Если передать объект в состоянииDetachedилиTransient, Hibernate немедленно выбросит исключение:java.lang.IllegalArgumentException: Entity not managed. - Затирает несохраненные данные: Все изменения полей объекта, сделанные в Java-коде до вызова
refresh(), безвозвратно теряются (если предварительно не был выполненflush()). - Обновляет снимок Dirty Checking: Снимок полей (
loadedState) внутри L1 кэша обновляется новыми значениями из БД.
СХЕМА РАБОТЫ МЕТОДА REFRESH()
[ Java Heap: User@0x1 ] [ База данных: users ]
name = "Изменено в памяти" id = 1, name = "Иван в БД"
│ │
│──── 1. entityManager.refresh(user) ─│
│ │
│<─── 2. SQL SELECT * WHERE id = 1 ───│
│ (Чтение актуальной строки) │
▼ ▼
[ Java Heap: User@0x1 ] [ База данных: users ]
name = "Иван в БД" id = 1, name = "Иван в БД"
(Snapshot обновлен, UPDATE отменен)
🟡 Middle Level
Когда refresh() действительно необходим в Production
- Работа триггеров базы данных:
Если в СУБД настроены триггеры (
BEFORE INSERT/BEFORE UPDATE), которые вычисляют колонки (например, генерируют промокод, статус или считают итоговую сумму), Hibernate не знает об этих изменениях.entityManager.persist(order); entityManager.flush(); // Физически отправляет INSERT, триггер БД срабатывает entityManager.refresh(order); // Загружает вычисленные триггером поля - Получение колонок со значениями по умолчанию (
DEFAULT): Когда колонка инициализируется базой данных (created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP), вызовrefresh()подтягивает сгенерированные СУБД таймстемпы. - После массовых обновлений (
Bulk Update/@Modifying): Пакетный запросUPDATE User u SET u.active = falseисполняется напрямую в базе данных, минуя L1 кэш. Ранее загруженные в сессию сущностиUserсодержат устаревшие поля. Вызовrefresh()возвращает им актуальное состояние.
refresh() с наложением блокировок (LockModeType)
Метод refresh() позволяет не просто перечитать данные, но и наложить на строку строгую блокировку в СУБД в рамках единого SQL-запроса:
// Блокировка на запись: SELECT ... FOR UPDATE
entityManager.refresh(account, LockModeType.PESSIMISTIC_WRITE);
// Блокировка с таймаутом ожидания освобождения строки
Map<String, Object> hints = Map.of("jakarta.persistence.lock.timeout", 3000); // 3 секунды
entityManager.refresh(account, LockModeType.PESSIMISTIC_WRITE, hints);
Каскадный refresh (CascadeType.REFRESH)
По умолчанию refresh(order) перезагружает только поля самой сущности Order. Связанные коллекции и зависимости не обновляются. Чтобы обновить весь граф зависимостей, требуется каскад:
@Entity
public class Order {
@Id
private Long id;
@OneToMany(mappedBy = "order", cascade = CascadeType.REFRESH)
private List<OrderItem> items = new ArrayList<>();
}
При вызове entityManager.refresh(order) Hibernate выполнит каскадный refresh() для каждого дочернего элемента OrderItem.
🔴 Senior Level
Внутренний механизм Hibernate: Что происходит со StatefulPersistenceContext
Внутри ядра Hibernate (SessionImpl) вызов refresh() обрабатывается слушателем DefaultRefreshEventListener:
- Проверяется статус в
entityEntries. Если сущность неStatus.MANAGED, выбрасываетсяIllegalArgumentException. - Если в
ActionQueueбыли накоплены незафиксированные действия над этой сущностью, они могут конфликтовать со сбросом. - Hibernate формирует SQL
SELECTсо всеми смаппленными колонками и исполняет его через JDBC. - Значения колонок извлекаются из
ResultSetи записываются в поля целевого Java-объекта (через прямой доступ к полям поField.set()или сеттеры). - Критический шаг: Массив
loadedStateвнутриEntityEntryполностью перезаписывается новыми значениями. Это гарантирует, что следующий запуск Dirty Checking посчитает сущность абсолютно «чистой» (not dirty). - Поведение коллекций (
PersistentCollection):- Если коллекция не была инициализирована (
uninitialized), она остается в виде ленивого прокси. - Если коллекция уже была загружена в память, Hibernate очищает ее элементы и повторно инициализирует свежими строками из БД.
- Если коллекция не была инициализирована (
Современная альтернатива: @Generated в Hibernate 6
В современных версиях Hibernate ручной вызов refresh() ради получения дефолтных колонок или значений триггеров устарел. Рекомендуется использовать аннотацию @Generated:
@Entity
public class Invoice {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// Hibernate 6 сам подтянет сгенерированное СУБД значение через RETURNING / дополнительный SELECT
@Generated(event = EventType.INSERT)
@Column(name = "created_at", insertable = false, updatable = false)
private Instant createdAt;
@Generated(event = {EventType.INSERT, EventType.UPDATE})
@Column(name = "total_amount", insertable = false, updatable = false)
private BigDecimal totalAmount;
}
При использовании @Generated Hibernate автоматически генерирует INSERT ... RETURNING created_at, total_amount (в PostgreSQL) или читает сгенерированные значения сразу после сохранения, устраняя необходимость в ручном flush() + refresh().
4 Tricky Questions
1. Что произойдет, если изменить поле сущности user.setEmail("new@mail.com"), затем вызвать entityManager.refresh(user), а в конце транзакции закоммитить изменения? Будет ли отправлен SQL UPDATE?
Ответ:
- SQL
UPDATEотправлен не будет. - Метод
refresh()выполняет немедленный SQLSELECT, читает старый email из БД и перезаписывает поле объекта в памяти JVM. - Одновременно с этим Hibernate обновляет снимок
loadedStateв контекстеPersistenceContext. - В конце транзакции Dirty Checking сравнивает текущее поле объекта (содержащее старый email) со снимком (также содержащим старый email). Отличий нет — никаких
UPDATEне планируется.
2. Что произойдет, если вызвать entityManager.refresh(user), но строка с этим ID была удалена в базе данных параллельной транзакцией?
Ответ:
- Метод
refresh()выполнитSELECT * FROM users WHERE id = ?. - База данных вернет пустой
ResultSet(0 строк). - Спецификация JPA требует в таком случае немедленно выбросить исключение:
jakarta.persistence.EntityNotFoundException: Entity no longer exists in the database. - Транзакция будет переведена в режим
Rollback-Only.
3. Мы выполнили запрос в Spring Data JPA: @Modifying @Query("UPDATE User u SET u.active = false"). Почему без вызова refresh() или специального параметра аннотации мы увидим старые данные?
Ответ:
- Аннотация
@Modifyingвыполняет прямой SQL/JPQLUPDATEна уровне СУБД, полностью минуяPersistenceContext(кэш L1). - Объекты
User, уже находящиеся в памяти текущей сессии, ничего не знают об этом изменении и продолжают хранитьactive = true. - Чтобы исправить рассинхронизацию, необходимо либо вызывать
entityManager.refresh(user)для каждой сущности, либо использовать параметр аннотации@Modifying(clearAutomatically = true). ПараметрclearAutomatically = trueочистит весь кэш L1, заставив следующийfind()прочитать свежие данные из базы.
4. Может ли вызов refresh() привести к NonUniqueObjectException, если в сессии есть связанные ленивые сущности?
Ответ:
- Нет, для самой обновляемой сущности это исключено благодаря Identity Map: обновляется именно существующий экземпляр в памяти по ссылке.
- Однако, если во время каскадного
refresh(order)перезагружается коллекция, содержащая дочерние элементы, ранее отсоединенные (Detached) и заново созданные в параллельном коде без участия сессии, попытка повторной привязки дублирующего экземпляра может привести к конфликту идентичности вPersistenceContext.
🎯 Шпаргалка для интервью
30-секундный ответ (Elevator Pitch)
«Метод
entityManager.refresh()принудительно перезагружает состояниеManagedсущности из базы данных, выполняя SQLSELECTпо первичному ключу. Он затирает любые несохраненные локальные изменения в полях объекта и обновляет снимокloadedStateдля Dirty Checking. Основные сценарии применения: подтягивание значений, сгенерированных триггерами БД или значениямиDEFAULT, синхронизация после нативных@Modifyingзапросов и блокировка строк черезrefresh(entity, LockModeType.PESSIMISTIC_WRITE). В современном Hibernate 6 необходимость вrefresh()для триггеров снизилась благодаря аннотации@Generated».
Ключевые метрики для Production-мониторинга
- Количество SELECT-запросов на один HTTP-запрос: частые вызовы
refresh()создают лишний сетевой оверхед на повторное чтение уже имеющихся данных. - EntityNotFoundException rate: всплеск ошибок указывает на удаление строк конкурентными процессами перед попыткой синхронизации.
Красные флаги (Чего говорить НЕЛЬЗЯ)
- ❌ «Вызываю refresh() после каждого persist(), чтобы проверить, что данные точно сохранились» — Антипаттерн, удваивающий количество запросов к БД без какой-либо пользы.
- ❌ «refresh() можно вызывать для Detached или DTO объектов» — Бросит
IllegalArgumentException, работает только дляManaged. - ❌ «refresh() отправляет локальные изменения из памяти в БД» — Ровно наоборот! Из памяти в БД отправляет
flush(), аrefresh()читает из БД и стирает изменения в памяти. - ❌ «refresh() автоматически коммитит транзакцию» — Нет, он только читает строку текущим соединением.