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

Что делает метод refresh()

Метод refresh() перезагружает сущность из базы данных, заменяя текущие значения в памяти актуальными данными из БД. Это полезно при работе с триггерами, конкурентными обновления...

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

Обзор

Метод refresh() перезагружает сущность из базы данных, заменяя текущие значения в памяти актуальными данными из БД. Это полезно при работе с триггерами, конкурентными обновлениями и когда нужно гарантировать актуальность данных.


🟢 Junior Level

Что делает refresh()

refresh() — перезагружает сущность из базы данных, заменяя текущие значения в памяти.

User user = entityManager.find(User.class, 1L);
user.setName("Changed in memory");  // изменили в памяти

// Другая транзакция изменила БД
// Или триггер изменил данные

entityManager.refresh(user);  // перезагрузить из БД
// Теперь user имеет актуальные значения из БД
// "Changed in memory" — перезаписано

Когда использовать

  1. После триггеров — база изменила поля через триггер
  2. При конкурентных обновлениях — другая транзакция обновила данные
  3. Для получения актуальных данных — когда есть сомнения в актуальности

refresh vs detach

refresh() — обновить данные из БД (сущность остаётся managed)
detach()  — отсоединить сущность (без обновления из БД)

Пример

@Transactional
public void updateUser(User user) {
    entityManager.persist(user);
    // Триггер в БД установил created_at
    entityManager.refresh(user);
    // Теперь user.getCreatedAt() содержит значение из БД
}

🟡 Middle Level

Детальное использование

// После триггера
User user = new User();
user.setName("John");
entityManager.persist(user);
// flush → триггер в БД установил created_at
entityManager.flush();
entityManager.refresh(user);  // получить created_at из БД

// При конкурентных обновлениях
User user = entityManager.find(User.class, 1L);
// Другая транзакция обновила user
entityManager.refresh(user);  // синхронизировать с БД

refresh с LockMode

// OPTIMISTIC — проверка version
entityManager.refresh(user, LockModeType.OPTIMISTIC);

// PESSIMISTIC_READ — блокировка на чтение
entityManager.refresh(user, LockModeType.PESSIMISTIC_READ);

// PESSIMISTIC_WRITE — блокировка на запись
entityManager.refresh(user, LockModeType.PESSIMISTIC_WRITE);

// С таймаутом
Map<String, Object> properties = Map.of(
    "jakarta.persistence.lock.timeout", 5000
);
entityManager.refresh(user, LockModeType.PESSIMISTIC_WRITE, properties);

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

// ❌ refresh без причины
User user = entityManager.find(User.class, 1L);
entityManager.refresh(user);  // ❌ лишний SELECT!

// ❌ refresh для detached
User detached = getDetachedUser();
entityManager.refresh(detached);  // ❌ IllegalArgumentException

// ✅ refresh только для managed
User managed = entityManager.find(User.class, 1L);
entityManager.refresh(managed);  // ✅

Когда refresh НЕ нужен

// ❌ Не нужен refresh после persist, если в БД нет триггеров/генерируемых колонок. Если триггер устанавливает поля — refresh нужен (см. Junior пример).
User user = new User();
entityManager.persist(user);
entityManager.refresh(user);  // ❌ данные те же самые

// ❌ Не нужен refresh после merge
User managed = entityManager.merge(detached);
entityManager.refresh(managed);  // ❌ данные уже актуальны

// ✅ Нужен после триггеров
entityManager.persist(user);
entityManager.flush();
entityManager.refresh(user);  // ✅ триггер мог изменить данные

🔴 Senior Level

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

refresh():
1. Проверить что entity managed (не detached)
2. Выполнить SELECT * FROM table WHERE id = ?
3. Обновить все поля entity из результата
4. Обновить snapshot в persistence context
5. Обновить EntityEntry

Если указан LockMode:
- Добавить LOCK clause к SELECT
- Проверить version для OPTIMISTIC

refresh и генерация ID

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(insertable = false, updatable = false)
    private LocalDateTime createdAt;  // установлено триггером
}

User user = new User();
entityManager.persist(user);
entityManager.flush();
entityManager.refresh(user);  // получить createdAt из БД

refresh и lazy loading

User user = entityManager.find(User.class, 1L);
user.getOrders().size();  // загрузить коллекцию

// Другая транзакция изменила коллекцию
entityManager.refresh(user);  // перезагрузить ВСЁ включая коллекции

// Или только коллекцию
entityManager.refresh(user, Map.of(
    "org.hibernate.refreshMode", RefreshMode.FETCH
));
// (Доступно в Hibernate 6+)

Продвинутые паттерны

// Pattern: refresh после bulk update
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.age > :age")
int bulkUpdate(@Param("status") String status, @Param("age") int age);

// После bulk update — refresh affected entities
User user = entityManager.find(User.class, userId);
entityManager.refresh(user);  // получить актуальные данные

Best Practices

✅ refresh() после триггеров БД
✅ refresh() при конкурентных изменениях
✅ С LockMode когда нужна блокировка
✅ flush() перед refresh() для синхронизации

❌ refresh() без причины (лишний SELECT)
❌ refresh() для detached entities
❌ refresh() вместо dirty checking
❌ refresh() после persist/merge без необходимости

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

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

  • refresh() перезагружает сущность из БД, заменяя текущие значения
  • Используется после триггеров, при конкурентных обновлениях, для актуальности данных
  • Поддерживает LockMode: OPTIMISTIC, PESSIMISTIC_READ, PESSIMISTIC_WRITE
  • Работает только для managed сущностей, для detached — IllegalArgumentException
  • В Hibernate 6+ есть RefreshMode.FETCH для загрузки только коллекций

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

  • Когда refresh() нужен? После триггеров БД, при конкурентных изменениях, после bulk update
  • refresh vs detach? refresh — обновляет данные из БД (остаётся managed), detach — отсоединяет (без обновления)
  • Почему refresh после persist бесполезен? Данные ещё не изменились, лишний SELECT
  • Можно ли refresh с таймаутом? Да, через properties: jakarta.persistence.lock.timeout

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

  • «refresh() после каждого persist» — лишний SELECT, данные те же
  • «refresh() для detached» — IllegalArgumentException
  • «refresh() вместо dirty checking» — dirty checking автоматический, refresh принудительный
  • «Не понимаю когда refresh нужен» — только триггеры, конкурентные изменения, bulk update

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

  • [[8. Что такое состояния transient, persistent, detached, removed]]
  • [[14. В чём разница между persist() и merge()]]
  • [[17. Как реализовать оптимистичную блокировку в JPA]]
  • [[18. Как реализовать пессимистичную блокировку в JPA]]