DEV Community

Cover image for Persistence Context в Hibernate Zoo: путешествие объекта по жизненным состояниям
Olga Lugacheva
Olga Lugacheva

Posted on

Persistence Context в Hibernate Zoo: путешествие объекта по жизненным состояниям

Добро пожаловать в Hibernate Zoo! Сегодня у нас увлекательное представление: встречайте Persistence Context! Этот веселый и усердный приятель — супергерой для ваших данных, сидящих в базе, и ловкач, который умеет управляться с хитрыми связями и объектами. Чтобы почувствовать себя настоящим исследователем, не забудьте надеть виртуальные очки Java, а код мы с вами закидаем прямо по ходу экскурсии.

Секреты за кулисами: что же такое Persistence Context?

Persistence Context — это, если уж говорить серьезно, некий контейнер для всех объектов, которые Hibernate отслеживает во время работы с базой данных. И как только вы начинаете транзакцию, Persistence Context тут как тут. Он наблюдает за каждым объектом, кэширует их, чтобы при повторных запросах не дергать базу данных понапрасну, и управляет состоянием всех сущностей.
Представьте его как упитанного енота с корзиной: он обходит все ваши объекты, с радостью добавляет их в корзинку, запоминает и хранит при себе, пока не закончится транзакция. А потом (бац!) он сохранит их в базе данных одним движением.

Жизненные стадии объектов в контексте персистенции Hibernate
В мире Hibernate объект может переходить из одного состояния в другое, и это критически важно для управления его жизненным циклом. Понимание этих стадий помогает эффективно управлять сохранением, обновлением и удалением данных. Итак, представьте, что ваш объект путешествует по четырём разным стадиям, как по уровням компьютерной игры.

Transient (Гость у ворот)

Когда объект только что создан и ещё не привязан ни к базе данных, ни к Hibernate-сессии, он находится в состоянии Transient (или "Переходное состояние"). Hibernate о нём ничего не знает, и, следовательно, он не будет сохранён в базе данных до тех пор, пока не попадёт под управление контекста персистенции.

Пример: Кот Бублик только появился

Cat cat = new Cat("Бублик", "серый");  // Transient
System.out.println("Имя кота: " + cat.getName());

Enter fullscreen mode Exit fullscreen mode

Persistent (Взяли под крылышко)

Теперь, когда вы решили, что ваш кот (или другой объект) заслуживает внимания и должен быть сохранён в базе, вы вызываете метод save() или persist(). После этого он становится persistent и Hibernate начинает за ним следить. Все изменения, которые вы будете вносить в этот объект, будут автоматически отслеживаться и сохранены в базе данных при закрытии транзакции.

Persistent cat
Пример: Бублик обрел дом в базе

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

Cat cat = new Cat("Бублик", "серый");
session.save(cat);  // Теперь кот стал persistent
cat.setColor("белый");  // Изменили цвет

session.getTransaction().commit();
session.close();

Enter fullscreen mode Exit fullscreen mode

Теперь кот Бублик persistent: Hibernate "запомнил" его и, когда вы закрываете транзакцию, изменения (например, смена цвета на белый) автоматически сохраняются в базе данных.

Detached (Бублик без надзора)

Когда сессия Hibernate закрывается (например, после вызова session.close()), объекты, которые были persistent, становятся detached. Это значит, что теперь они больше не отслеживаются Hibernate и никакие изменения, которые вы внесете в них, не будут автоматически сохранены.

Представьте, что кот Бублик убежал на улицу, и теперь Hibernate за ним больше не присматривает.

Пример: Бублик уходит, но он уже не в контексте

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

Cat cat = session.get(Cat.class, 1);  // Кот уже есть в базе и стал persistent
session.getTransaction().commit();
session.close();  // Теперь он detached

cat.setColor("черный");  // Изменение цвета больше не отслеживается Hibernate

Enter fullscreen mode Exit fullscreen mode

После закрытия сессии кот Бублик больше не находится под контролем Hibernate. Его статус detached, и если мы изменим его цвет на "черный", это не сохранится в базе данных.

Вернуть кота обратно в Hibernate
Чтобы снова привязать объект к контексту персистенции, можно использовать метод merge()

Session newSession = sessionFactory.openSession();
newSession.beginTransaction();

Cat mergedCat = (Cat) newSession.merge(cat);  // Бублик снова под контролем Hibernate
mergedCat.setColor("рыжий");  // Hibernate снова отслеживает изменения

newSession.getTransaction().commit();
newSession.close();
Enter fullscreen mode Exit fullscreen mode

После вызова merge() Hibernate снова начинает следить за Бубликом, и теперь все изменения будут сохранены.

Removed

Когда вы решаете, что объект больше не нужен в базе данных, вы можете пометить его для удаления с помощью session.delete(). Это переводит объект в состояние Removed. Объект ещё может существовать в памяти, но как только транзакция завершится, он исчезнет из базы данных. Итак, наш герой отправился вновь на поиски приключений.

Пример: Путь на свободу

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

Cat cat = session.get(Cat.class, 1);  // Бублик под контролем Hibernate
session.delete(cat);  // Пометили на удаление

session.getTransaction().commit();  // Бублик удалён из базы
session.close();

Enter fullscreen mode Exit fullscreen mode

Как только мы вызвали delete(), объект стал Removed. После завершения транзакции Бублик будет удалён из базы данных навсегда.

Схема переходов состояний в Hibernate

Чтобы наглядно представить переходы между состояниями, можно использовать следующую схему:

TransientPersistent: объект сохраняется в базе данных с помощью session.save() или session.persist().
PersistentDetached: сессия закрывается, и объект выходит из-под контроля Hibernate.
DetachedPersistent: объект снова привязывается к Hibernate с помощью session.merge() или session.update().
PersistentRemoved: объект помечается для удаления с помощью session.delete().
perscontext

Методы для Управления Жизненным Циклом Объектов в Hibernate
Hibernate предоставляет множество методов для управления состояниями объектов. От создания до удаления, каждый метод влияет на объект и его связь с базой данных через Persistence Context. Давайте разберемся в каждом методе, их особенностях, типичных ошибках и полезных примерах.

Приведем основные методы Hibernate для управления жизненным циклом

save()
Назначение:
Сохраняет объект в базу данных, переводя его из Transient в Persistent.

Ключевые особенности:

Генерирует SQL-запрос INSERT, добавляя объект в базу.
Возвращает идентификатор (id), сгенерированный для объекта.
Не требует, чтобы объект уже существовал в базе

Cat murzik = new Cat("Мурзик", "серый"); // Transient
session.save(murzik); // Теперь murzik — Persistent
Enter fullscreen mode Exit fullscreen mode

Типичная ошибка:
Попытка вызвать save() для объекта с уже установленным идентификатором может вызвать ошибки из-за конфликта id.

persist()
Назначение:
Также переводит объект в состояние Persistent.

Отличие от save():

persist() ничего не возвращает.
Используется только для объектов, которые не имеют идентификатора (новые).
Пример:

Cat barsik = new Cat("Барсик", "рыжий");
session.persist(barsik); // Добавлен в Persistence Context
Enter fullscreen mode Exit fullscreen mode

Типичная ошибка:
persist() нельзя использовать для объектов, находящихся в состоянии Detached.

update()
Назначение:
Привязывает объект в состоянии Detached к текущей сессии и переводит его в Persistent.

Пример:

session.close(); // Объект стал Detached
session = factory.openSession();
session.update(detachedCat); // Привязываем обратно

Enter fullscreen mode Exit fullscreen mode

merge()
Назначение:
Объединяет изменения в объекте в состоянии Detached с текущим состоянием в Persistence Context.

Пример:

Cat detachedCat = session.get(Cat.class, 1);
session.close(); // Detached
detachedCat.setColor("белый");

session = factory.openSession();
session.merge(detachedCat); // Объединяет изменения с базой
Enter fullscreen mode Exit fullscreen mode

Отличие от update():

merge() создаёт новый объект, если Detached-объект уже существует в Persistence Context.
Это делает merge() более безопасным.

delete()
Назначение:
Удаляет объект из базы данных и переводит его в состояние Removed.

Пример:

session.delete(murzik); // Удаляет объект из базы
Enter fullscreen mode Exit fullscreen mode

Особенность:
После удаления объект остаётся в памяти, но больше не привязан к Persistence Context.

flush()
Назначение:
Принудительно синхронизирует состояние Persistence Context с базой данных.

Пример:

session.save(cat);
session.flush(); // Все изменения записаны в базу

Enter fullscreen mode Exit fullscreen mode

Типичная ошибка:
Если между flush() и commit() произойдёт ошибка, база останется несинхронизированной.

saveOrUpdate()
Назначение:
Сохраняет новый объект или обновляет существующий.

Пример:

Cat cat = new Cat("Барсик", "рыжий");
cat.setId(1); // Если id существует, объект будет обновлён
session.saveOrUpdate(cat);

Enter fullscreen mode Exit fullscreen mode

Особенность:

Используйте этот метод, если не уверены, новый объект или уже существующий.
Но это может вызвать больше запросов, чем нужно.

clear()
Назначение:
Очищает Persistence Context, удаляя все объекты из него.

Пример:

session.clear(); // Все объекты становятся Detached
Enter fullscreen mode Exit fullscreen mode

Типичная ошибка:
После clear() нельзя обращаться к объектам, ожидая, что они останутся Persistent.

detach()
Назначение:
Превращает объект в состояние Detached, исключая его из Persistence Context.

Пример:

session.detach(cat); // Теперь cat — Detached
Enter fullscreen mode Exit fullscreen mode

close()
Назначение:
Закрывает сессию Hibernate, делая все объекты Detached.

Пример: session.close();

Сложности и Типичные Ошибки

  • Ошибка при использовании update(): Если объект с таким же идентификатором уже существует в Persistence Context, Hibernate выбросит исключение. Решение: Использовать merge(), если не уверены в состоянии объекта.

Потеря данных из-за flush():
Если вызвать flush() перед commit(), изменения могут записаться в базу, но не будут зафиксированы.
Решение:
Использовать commit() сразу после flush().

  • Дублирование при использовании save():
    Если вы вызываете save() для объекта с установленным id, это может привести к нарушению уникальности.
    Решение:
    Проверяйте, существует ли объект, прежде чем вызывать save().

  • Ленивая загрузка (LazyInitializationException):
    Ошибка возникает, если вы пытаетесь получить доступ к связанным объектам после закрытия сессии.
    Решение:
    Использовать fetch=FetchType.EAGER.
    Загружать данные заранее с помощью JOIN FETCH.

Путешествие объектов в Hibernate Zoo — это захватывающее приключение, где на каждом этапе Бублик и его друзья попадают в разные состояния и взаимодействуют с базой данных через Persistence Context. В начале они свободны, как птицы (или коты!), потом попадают под строгий надзор, выходят в свободное плавание и, в конце концов, могут покинуть этот зоопарк навсегда.

Top comments (0)