1. Введение
Hibernate — это удобный фреймворк для управления постоянными данными, но иногда бывает сложно понять, как он работает внутри.
В этом уроке мы узнаем о состояниях объектов и о том, как перемещаться между ними. Мы также рассмотрим проблемы, с которыми мы можем столкнуться с отсоединенными объектами, и способы их решения.
2. Сессия Hibernate
Интерфейс сеанса
является основным инструментом, используемым для связи с Hibernate. Он предоставляет API, позволяющий нам создавать, читать, обновлять и удалять постоянные объекты. Сессия имеет
простой жизненный цикл. Открываем его, выполняем какие-то операции, а потом закрываем.
Когда мы работаем с объектами во время сеанса
, они привязываются к этому сеансу
. Вносимые нами изменения обнаруживаются и сохраняются при закрытии. После закрытия Hibernate разрывает соединения между объектами и сеансом.
3. Состояния объекта
В контексте сеанса
Hibernate объекты могут находиться в одном из трех возможных состояний: переходном, постоянном или отсоединенном.
3.1. Переходный
Объект, который мы не привязали ни к одному сеансу
, находится в переходном состоянии. Поскольку он никогда не сохранялся, он не представлен в базе данных. Поскольку ни один сеанс
не знает об этом, он не будет сохранен автоматически.
Давайте создадим пользовательский объект с помощью конструктора и подтвердим, что он не управляется сеансом:
Session session = openSession();
UserEntity userEntity = new UserEntity("John");
assertThat(session.contains(userEntity)).isFalse();
3.2. Настойчивый
Объект, который мы связали с сеансом
, находится в постоянном состоянии. Мы либо сохранили его, либо прочитали из контекста персистентности, поэтому он представляет некоторую строку в базе данных.
Давайте создадим объект, а затем используем метод persist
, чтобы сделать его постоянным:
Session session = openSession();
UserEntity userEntity = new UserEntity("John");
session.persist(userEntity);
assertThat(session.contains(userEntity)).isTrue();
В качестве альтернативы мы можем использовать метод сохранения
. Разница в том, что метод persist
просто сохранит объект, а метод save
дополнительно сгенерирует его идентификатор, если это необходимо.
3.3. Отдельный
Когда мы закрываем сеанс
, все объекты внутри него становятся отсоединенными. Хотя они по-прежнему представляют строки в базе данных, они больше не управляются никаким сеансом
:
session.persist(userEntity);
session.close();
assertThat(session.isOpen()).isFalse();
assertThatThrownBy(() -> session.contains(userEntity));
Далее мы узнаем, как сохранять временные и отсоединенные объекты.
4. Сохранение и повторное присоединение объекта
4.1. Сохранение временного объекта
Давайте создадим новую сущность и сохраним ее в базе данных. Когда мы впервые создадим объект, он будет находиться в переходном состоянии.
Чтобы сохранить
нашу новую сущность, мы будем использовать метод persist
:
UserEntity userEntity = new UserEntity("John");
session.persist(userEntity);
Теперь мы создадим еще один объект с тем же идентификатором, что и первый. Этот второй объект является временным, поскольку он еще не управляется никаким сеансом
, но мы не можем сделать его постоянным с помощью метода persist
. Он уже представлен в базе данных, так что он не совсем новый в контексте уровня сохраняемости.
Вместо этого мы будем использовать метод слияния
, чтобы обновить базу данных и сделать объект постоянным :
UserEntity onceAgainJohn = new UserEntity("John");
session.merge(onceAgainJohn);
4.2. Сохранение отсоединенного объекта
Если мы закроем предыдущую сессию
, наши объекты будут в отсоединенном состоянии. Как и в предыдущем примере, они представлены в базе данных, но в настоящее время не управляются никаким сеансом
. Мы можем снова сделать их постоянными, используя метод слияния
:
UserEntity userEntity = new UserEntity("John");
session.persist(userEntity);
session.close();
session.merge(userEntity);
5. Вложенные объекты
Все становится сложнее, когда мы рассматриваем вложенные сущности. Допустим, наша сущность пользователя также будет хранить информацию о его менеджере:
public class UserEntity {
@Id
private String name;
@ManyToOne
private UserEntity manager;
}
Когда мы сохраняем эту сущность, нам нужно думать не только о состоянии самой сущности, но и о состоянии вложенной сущности. Давайте создадим постоянный пользовательский объект, а затем установим его менеджера:
UserEntity userEntity = new UserEntity("John");
session.persist(userEntity);
UserEntity manager = new UserEntity("Adam");
userEntity.setManager(manager);
Если мы попытаемся обновить его сейчас, мы получим исключение:
assertThatThrownBy(() -> {
session.saveOrUpdate(userEntity);
transaction.commit();
});
java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.foreach.states.UserEntity.manager -> com.foreach.states.UserEntity
Это происходит потому, что Hibernate не знает, что делать с временным вложенным объектом.
5.1. Сохранение вложенных объектов
Одним из способов решения этой проблемы является явное сохранение вложенных сущностей:
UserEntity manager = new UserEntity("Adam");
session.persist(manager);
userEntity.setManager(manager);
Затем, после совершения транзакции, мы сможем получить правильно сохраненную сущность:
transaction.commit();
session.close();
Session otherSession = openSession();
UserEntity savedUser = otherSession.get(UserEntity.class, "John");
assertThat(savedUser.getManager().getName()).isEqualTo("Adam");
5.2. Каскадные операции
Временные вложенные объекты могут сохраняться автоматически, если мы правильно настроим свойство каскада отношения в классе объектов:
@ManyToOne(cascade = CascadeType.PERSIST)
private UserEntity manager;
Теперь, когда мы сохраняем объект, эта операция будет каскадирована на все вложенные сущности:
UserEntityWithCascade userEntity = new UserEntityWithCascade("John");
session.persist(userEntity);
UserEntityWithCascade manager = new UserEntityWithCascade("Adam");
userEntity.setManager(manager); // add transient manager to persistent user
session.saveOrUpdate(userEntity);
transaction.commit();
session.close();
Session otherSession = openSession();
UserEntityWithCascade savedUser = otherSession.get(UserEntityWithCascade.class, "John");
assertThat(savedUser.getManager().getName()).isEqualTo("Adam");
6. Резюме
В этом руководстве мы более подробно рассмотрели, как сеанс
гибернации работает в отношении состояния объекта. Затем мы рассмотрели некоторые проблемы, которые он может создать, и способы их решения.
Как всегда, исходный код доступен на GitHub .