Перейти к основному содержимому

Жизненный цикл объекта Hibernate

· 6 мин. чтения

Задача: Сумма двух чисел

Напишите функцию twoSum. Которая получает массив целых чисел nums и целую сумму target, а возвращает индексы двух чисел, сумма которых равна target. Любой набор входных данных имеет ровно одно решение, и вы не можете использовать один и тот же элемент дважды. Ответ можно возвращать в любом порядке...

ANDROMEDA

1. Обзор

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

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

Чтобы узнать о различных методах Hibernate, работающих с сущностями, ознакомьтесь с одним из наших предыдущих руководств .

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

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

  • HibernateLifecycleUtil.getManagedEntities(session) — мы будем использовать его для получения всех управляемых объектов из внутреннего хранилища сеанса.
  • DirtyDataInspector.getDirtyEntities() — мы собираемся использовать этот метод для получения списка всех сущностей, помеченных как «грязные».
  • HibernateLifecycleUtil.queryCount(query) — удобный метод выполнения запроса count(*) к встроенной базе данных.

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

3. Все дело в контексте персистентности

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

Проще говоря, контекст персистентности находится между клиентским кодом и хранилищем данных . Это промежуточная область, где постоянные данные преобразуются в сущности, готовые для чтения и изменения клиентским кодом.

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

JPA EntityManager и сеанс Hibernate являются реализацией концепции контекста персистентности . На протяжении всей этой статьи мы будем использовать Hibernate Session для представления контекста персистентности.

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

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

Управляемый объект — это представление строки таблицы базы данных (хотя эта строка еще не обязательно должна существовать в базе данных).

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

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

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

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

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

Допустим, мы хотим изменить имя Буффона для начала — мы хотим ввести его полное имя Джанлуиджи Буффон вместо Джиджи Буффон.

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

Session session = sessionFactory.openSession();

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

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

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

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

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

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

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

Внутри Session отслеживает все объекты, которые он загружает в хранилище постоянного контекста. В нашем случае внутреннее хранилище сеанса будет содержать 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 о том, что мы что-то изменили в нашей сущности — поскольку это управляемая сущность, все изменения автоматически распространяются в базу данных.

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

См. главу о переходных сущностях ниже.

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

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

Сущность может стать отсоединенной, когда сессия , используемая для ее загрузки, была закрыта или когда мы вызываем 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) может (повторно) прикрепить сеанс :

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() см . здесь .

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

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

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

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

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

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

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

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

6. Преходящая сущность

Временная сущность — это просто объект сущности , который не имеет представления в постоянном хранилище и не управляется никакой сессией .

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

Чтобы сделать временную сущность постоянной , нам нужно вызвать Session.save(entity) или Session.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. Удаленный объект

Объект находится в удаленном (удаленном) состоянии, если `` был вызван Session.delete(entity) и сеанс пометил объект для удаления. Сама команда DELETE может быть выполнена в конце единицы работы.

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

session.delete(neymar);

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

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

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

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

Как обычно, код, использованный в этой статье, можно найти на GitHub .