← Назад к вопросам

В чем разница между состояниями Hibernate?

2.0 Middle🔥 151 комментариев
#ORM и Hibernate

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Состояния объектов в Hibernate

В Hibernate каждый объект находится в одном из четырёх состояний, которые определяют его взаимодействие с сессией и БД. Понимание этих состояний критично для правильной работы с ORM.

Четыре состояния объектов в Hibernate

1. Transient (Новый, временный)

Transient — объект, который создан в памяти Java, но не связан ни с какой сессией Hibernate и не существует в БД.

User user = new User();        // Создаём новый объект
user.setName("John");
user.setEmail("john@example.com");

// На этом этапе объект TRANSIENT:
// - не привязан к сессии
// - нет entry в identity map сессии
// - нет primary key
// - не синхронизирован с БД

Характеристики transient объекта:

  • Создан в Java памяти
  • Не имеет primary key (или null)
  • Независим от сессии
  • При GC будет удален, если нет ссылок

Как попасть в Persistent:

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

User user = new User();
user.setName("John");

// Переводим в Persistent через save, persist или saveOrUpdate
session.save(user);          // Присвоит ID и добавит в сессию
// или
session.persist(user);       // JPA стандарт
// или  
session.saveOrUpdate(user);  // Универсальный метод

tx.commit();

2. Persistent (Управляемый)

Persistent — объект, который связан с активной сессией Hibernate и синхронизирован с БД.

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

User user = new User();
user.setName("John");
session.save(user);  // Переходит в PERSISTENT

// Теперь объект:
// - имеет ID
// - находится в identity map сессии
// - при изменениях отследит Hibernate (Dirty Checking)
// - при commit() изменения сохранятся в БД

user.setEmail("john@example.com");
// Hibernate автоматически обнаружит изменение

tx.commit();  // UPDATE запрос выполнится
session.close();

Получение объекта в Persistent:

Session session = sessionFactory.openSession();

// get() возвращает объект в состоянии PERSISTENT
User user = session.get(User.class, 1);
// user находится в PERSISTENT и связан с сессией

session.close();

Характеристики persistent объекта:

  • Связан с активной сессией
  • Имеет primary key (отличный от null)
  • Находится в identity map сессии
  • Hibernate отслеживает изменения (Dirty Checking)
  • При commit() изменения будут синхронизированы с БД
  • Все связанные объекты могут быть загружены (зависит от fetch strategy)

3. Detached (Отсоединённый)

Detached — объект, который раньше был Persistent, но сессия закрыта и объект больше не связан ни с какой активной сессией.

Session session1 = sessionFactory.openSession();
Transaction tx1 = session1.beginTransaction();

User user = session1.get(User.class, 1);
// user is PERSISTENT

tx1.commit();
session1.close();  // Сессия закрывается

// Теперь user находится в DETACHED состоянии:
// - имеет ID
// - не связан ни с какой сессией
// - изменения НЕ отслеживаются
// - при изменении свойств это НЕ отразится на БД

user.setName("Jane");  // Это изменение потеряется!

// Для сохранения нужно переподключить сессию
Session session2 = sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();

session2.merge(user);  // Переводит в PERSISTENT
// или
session2.update(user); // Альтернатива для known objects

tx2.commit();
session2.close();

Проблема N+1 с detached объектами:

Session session = sessionFactory.openSession();
List<User> users = session.createQuery("FROM User").list();
session.close();

// users теперь DETACHED
// Если попробуем доступ к lazy-loaded коллекциям:
for (User user : users) {
    System.out.println(user.getPosts());  // LazyInitializationException!
    // Потому что сессия закрыта и Hibernate не может загрузить posts
}

Решение: Eager Loading

Session session = sessionFactory.openSession();
List<User> users = session.createQuery(
    "SELECT u FROM User u JOIN FETCH u.posts"
).list();
session.close();

// Теперь posts уже загружены и доступны
for (User user : users) {
    System.out.println(user.getPosts());  // OK
}

Характеристики detached объекта:

  • Имеет primary key
  • НЕ связан ни с какой сессией
  • Изменения НЕ отслеживаются
  • Lazy-loaded коллекции недоступны
  • Может быть переподключен через merge() или update()

4. Removed (Удалённый)

Removed — объект, который был удален из БД в текущей транзакции.

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

User user = session.get(User.class, 1);
// user is PERSISTENT

session.delete(user);  // Переводит в REMOVED

// На этом этапе:
// - объект помечен для удаления
// - при commit() будет выполнена DELETE команда
// - Hibernate уже не отслеживает изменения

user.setName("Jane");  // Бесполезно, объект удалится

tx.commit();
// DELETE FROM users WHERE id = 1

session.close();
// После commit user находится в DETACHED состоянии

Характеристики removed объекта:

  • Был PERSISTENT, теперь помечен для удаления
  • Находится в сессии до commit
  • При commit выполнится DELETE запрос
  • После commit переходит в DETACHED

Диаграмма переходов состояний

NEW (Transient)
    ↓
    ← save(), persist()
    ↓
PERSISTENT ←──────→ DETACHED
    ↓                    ↑
    → delete()           ← close(), evict()
    ↓                    ← merge(), update()
    ↓
REMOVED
    ↓
    → commit()
    ↓
DETACHED

Практические примеры

Пример 1: Session-per-transaction (правильный паттерн)

public class UserService {
    @Transactional  // Управление сессией автоматически
    public void updateUser(Long id, String newName) {
        User user = entityManager.find(User.class, id);
        // PERSISTENT
        
        user.setName(newName);
        // Dirty checking отследит изменение
        
        // При выходе из метода commit() выполнится UPDATE
    }
}

Пример 2: Неправильная работа с detached

// ПЛОХО
public void updateUserBad(Long id, String newName) {
    User user = entityManager.find(User.class, id);
    entityManager.close();  // Сессия закрыта, user -> DETACHED
    
    user.setName(newName);  // Это изменение потеряется!
    
    EntityManager em2 = emf.createEntityManager();
    em2.getTransaction().begin();
    em2.persist(user);  // Попытка вставить как новый объект
    // Ошибка: первичный ключ существует!
}

// ХОРОШО
public void updateUserGood(Long id, String newName) {
    EntityManager em = emf.createEntityManager();
    em.getTransaction().begin();
    
    User user = em.find(User.class, id);
    user.setName(newName);  // PERSISTENT
    
    em.getTransaction().commit();
    em.close();
}

Пример 3: merge() для detached

User user = new User();
user.setId(1L);
user.setName("Jane");
// user is DETACHED (имеет ID, но не связан с сессией)

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

User merged = session.merge(user);
// merged is PERSISTENT
// user остаётся DETACHED

merged.setEmail("jane@example.com");  // Отследится

tx.commit();
session.close();

Разница между методами сохранения

Методsave()persist()update()merge()saveOrUpdate()
TransientГенерирует IDНе генерируетОшибкаКопируетГенерирует
Detached с IDОшибкаОшибкаOKOKOK
Новый с IDСохраняетСохраняетОшибкаКопируетСохраняет
СтандартHibernateJPAHibernateJPAHibernate

Практические советы

  • Используй @Transactional: Spring управляет сессией для тебя
  • Избегай detached объектов: работай в одной транзакции
  • merge() для detached: если нужно переподключить
  • Lazy loading опасен: используй JOIN FETCH или @EntityGraph
  • Следи за identity map: session.clear() может потребоваться для больших батчей

Понимание состояний Hibernate — это ключ к написанию эффективного и правильного кода при работе с БД.