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. В начале они свободны, как птицы (или коты!), потом попадают под строгий надзор, выходят в свободное плавание и, в конце концов, могут покинуть этот зоопарк навсегда.

Image of Docusign

Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay