Жизненный цикл Hibernate Entity

Hibernate Entity Lifecycle

1. обзор

У каждой сущности Hibernate естественным образом есть жизненный цикл в рамках - он находится в переходном, управляемом, отсоединенном или удаленном состоянии.

Понимание этих состояний как на концептуальном, так и на техническом уровне важно для правильного использования Hibernate.

Чтобы узнать о различных методах Hibernate, которые работают с сущностями, взгляните наone of our previous tutorials.

2. Вспомогательные методы

В этом руководстве мы будем последовательно использовать несколько вспомогательных методов:

  • HibernateLifecycleUtil.getManagedEntities(session) – мы будем использовать его, чтобы получить все управляемые объекты из внутреннего хранилищаSession’s

  • DirtyDataInspector.getDirtyEntities() – мы собираемся использовать этот метод, чтобы получить список всех объектов, которые были помечены как "грязные"

  • HibernateLifecycleUtil.queryCount(query) – - удобный метод выполнения запросаcount(*) к встроенной базе данных

Все вышеперечисленные вспомогательные методы статически импортированы для лучшей читаемости. Вы можете найти их реализации в проекте GitHub, ссылки на которые приведены в конце этой статьи.

3. Все дело в контексте сохранения

Прежде чем перейти к теме жизненного цикла сущностей, сначала нам нужно понятьthe persistence context.

Simply put, the persistence context sits between client code and data store. Это промежуточная область, где постоянные данные преобразуются в объекты, готовые к чтению и изменению клиентским кодом.

Теоретическиpersistence context  является реализацией шаблонаUnit of Work. Он отслеживает все загруженные данные, отслеживает изменения этих данных и в конечном итоге отвечает за синхронизацию любых изменений обратно в базу данных в конце бизнес-транзакции.

JPAEntityManager и HibernateSession  являются реализацией концепцииpersistence context . В этой статье мы будем использовать HibernateSession для представленияpersistence context..

Состояние жизненного цикла объекта Hibernate объясняет, как объект связан сpersistence context, как мы увидим дальше.

4. Управляемая организация

A managed entity is a representation of a database table row (хотя эта строка еще не должна существовать в базе данных).

Это управляется текущими запущеннымиSession иevery change made on it will be tracked and propagated to the database automatically.

Session либо загружает объект из базы данных, либо повторно подключает отсоединенный объект. Мы обсудим отдельные объекты в разделе 5.

Давайте рассмотрим код, чтобы прояснить ситуацию.

Наш пример приложения определяет одну сущность, классFootballPlayer. При запуске мы инициализируем хранилище данных некоторыми примерами данных:

+-------------------+-------+
| Name              |  ID   |
+-------------------+-------+
| Cristiano Ronaldo | 1     |
| Lionel Messi      | 2     |
| Gianluigi Buffon  | 3     |
+-------------------+-------+

Предположим, мы хотим изменить имя Буффона для начала - мы хотим указать его полное имяGianluigi Buffon вместо Джиджи Буффон.

Во-первых, нам нужно начать нашу единицу работы с полученияSession:

Session session = sessionFactory.openSession();

В серверной среде мы можем внедритьSession в наш код через контекстно-зависимый прокси. Принцип остается тем же: нам нуженSession to, инкапсулирующий бизнес-транзакцию нашей единицы работы.

Затем мы проинструктируем нашSession загрузить данные из постоянного хранилища:

assertThat(getManagedEntities(session)).isEmpty();

List players = s.createQuery("from FootballPlayer").getResultList();

assertThat(getManagedEntities(session)).size().isEqualTo(3);

Когда мы впервые получаемSession, его постоянное хранилище контекста пусто, как показывает наш первый операторassert.

Затем мы выполняем запрос, который извлекает данные из базы данных, создает представление данных в виде сущности и, наконец, возвращает сущность, которую мы можем использовать.

ВнутриSession keeps отслеживает все сущности, которые он загружает в постоянное хранилище контекста. В нашем случае синтернальное хранилищеSession’s будет содержать 3 сущности после запроса.

Теперь давайте изменим имя Джиджи:

Transaction transaction = session.getTransaction();
transaction.begin();

FootballPlayer gigiBuffon = players.stream()
  .filter(p -> p.getId() == 3)
  .findFirst()
  .get();

gigiBuffon.setName("Gianluigi Buffon");
transaction.commit();

assertThat(getDirtyEntities()).size().isEqualTo(1);
assertThat(getDirtyEntities().get(0).getName()).isEqualTo("Gianluigi Buffon");

4.1. Как это работает?

При вызове транзакцииcommit() илиflush(),Session найдет любые объектыdirty из своего списка отслеживания и синхронизирует состояние с базой данных.

Обратите внимание, что нам не нужно было вызывать какой-либо метод для уведомленияSession о том, что мы что-то изменили в нашей сущности - поскольку это управляемая сущность, все изменения автоматически переносятся в базу данных.

Управляемый объект всегда является постоянным объектом - он должен иметь идентификатор базы данных, даже если представление строки базы данных еще не создано, т.е. оператор INSERT ожидает окончания единицы работы.

Смотрите главу о переходных объектах ниже.

5. Отдельная сущность

detached entity is just an ordinary entity POJO, значение идентификатора которого соответствует строке базы данных. Отличие от управляемого объекта в том, что этоnot tracked anymore by any persistence context.

Сущность может быть отсоединена, когдаSession, использованный для ее загрузки, был закрыт, или когда мы вызываемSession.evict(entity) илиSession.clear().

Посмотрим на код:

FootballPlayer cr7 = session.get(FootballPlayer.class, 1L);

assertThat(getManagedEntities(session)).size().isEqualTo(1);
assertThat(getManagedEntities(session).get(0).getId()).isEqualTo(cr7.getId());

session.evict(cr7);

assertThat(getManagedEntities(session)).size().isEqualTo(0);

Наш контекст постоянства не будет отслеживать изменения в отдельных объектах:

cr7.setName("CR7");
transaction.commit();

assertThat(getDirtyEntities()).isEmpty();

Session.merge(entity)/Session.update(entity) can  (повторно) присоединить сеанс:

FootballPlayer messi = session.get(FootballPlayer.class, 2L);

session.evict(messi);
messi.setName("Leo Messi");
transaction.commit();

assertThat(getDirtyEntities()).isEmpty();

transaction = startTransaction(session);
session.update(messi);
transaction.commit();

assertThat(getDirtyEntities()).size().isEqualTo(1);
assertThat(getDirtyEntities().get(0).getName()).isEqualTo("Leo Messi");

Для справки поSession.merge() иSession.update() см.here.

5.1. Поле идентичности - это все, что имеет значение

Давайте посмотрим на следующую логику:

FootballPlayer gigi = new FootballPlayer();
gigi.setId(3);
gigi.setName("Gigi the Legend");
session.update(gigi);

В приведенном выше примере мы создали экземпляр объекта обычным способом с помощью его конструктора. Мы заполнили поля значениями и установили для идентификатора значение 3, что соответствует идентификатору постоянных данных, принадлежащих Джиджи Буффон. Вызовupdate() имеет точно такой же эффект, как если бы мы загрузили объект из другогоpersistence context.

Фактически,Session  не различает, откуда произошла повторно присоединенная сущность.

В веб-приложениях довольно распространен сценарий создания отдельных сущностей из значений HTML-формы.

Что касаетсяSession, отсоединенный объект - это просто объект, значение идентификатора которого соответствует постоянным данным.

Имейте в виду, что приведенный выше пример просто служит демонстрационной цели. и нам нужно точно знать, что мы делаем. В противном случае мы могли бы получить нулевые значения в нашей сущности, если бы мы просто установили значение в поле, которое мы хотим обновить, оставив остальное нетронутым (то есть фактически пустым).

6. Временная сущность

A transient entity is simply an entity object that has no representation in the persistent store и не управляется никакимSession.

Типичным примером временного объекта может быть создание нового объекта через его конструктор.

Чтобы создать временный объектpersistent, нам нужно вызватьSession.save(entity) orSession.saveOrUpdate(entity):

FootballPlayer neymar = new FootballPlayer();
neymar.setName("Neymar");
session.save(neymar);

assertThat(getManagedEntities(session)).size().isEqualTo(1);
assertThat(neymar.getId()).isNotNull();

int count = queryCount("select count(*) from Football_Player where name='Neymar'");

assertThat(count).isEqualTo(0);

transaction.commit();
count = queryCount("select count(*) from Football_Player where name='Neymar'");

assertThat(count).isEqualTo(1);

Как только мы выполняемSession.save(entity), объекту присваивается значение идентичности, и он становится управляемымSession. Однако он может быть еще недоступен в базе данных, поскольку операция INSERT может быть отложена до конца единицы работы.

7. Удаленный объект

ВызванAn entity is in a deleted (removed) state if Session.delete(entity), иSession has пометили объект для удаления. Сама команда DELETE может быть введена в конце единицы работы.

Давайте посмотрим на это в следующем коде:

session.delete(neymar);

assertThat(getManagedEntities(session).get(0).getStatus()).isEqualTo(Status.DELETED);

Однако обратите внимание, что объект остается в постоянном хранилище контекста до конца единицы работы.

8. Заключение

Концепцияpersistence context является центральной для понимания жизненного цикла сущностей Hibernate. Мы прояснили жизненный цикл, изучив примеры кода, демонстрирующие каждый статус.

Как обычно, код, использованный в этой статье, находится вover on GitHub.