Рубрики
Без рубрики

Режим гибернации: сохранение, сохранение, обновление, объединение, сохранение или обновление

Краткое и практическое руководство по методам записи в спящий режим: сохранение, сохранение, обновление, объединение, сохранение и обновление.

Автор оригинала: baeldung.

1. введение

В этой статье мы обсудим различия между несколькими методами интерфейса Сеанса : сохранить , сохранить , обновить , объединить , Сохранить обновление .

Это не введение в режим гибернации, и вы уже должны знать основы конфигурации, объектно-реляционного сопоставления и работы с экземплярами сущностей. Для получения вводной статьи о гибернации посетите наш учебник по гибернации 4 с помощью Spring .

Дальнейшее чтение:

Удаление объектов с помощью режима гибернации

Хранимые процедуры с режимом гибернации

Обзор идентификаторов в режиме гибернации/JPA

2. Сессия как реализация контекста сохранения

Интерфейс Session имеет несколько методов, которые в конечном итоге приводят к сохранению данных в базе данных: сохранение , сохранение , обновление , слияние , Сохранение | обновление|/. Чтобы понять разницу между этими методами, мы должны сначала обсудить цель Сеанса как контекста сохранения и разницу между состояниями экземпляров сущностей по отношению к Сеансу .

Мы также должны понять историю разработки Hibernate, которая привела к некоторым частично дублированным методам API.

2.1. Управление Экземплярами Сущностей

Помимо самого объектно-реляционного отображения, одной из проблем, для решения которой был предназначен Hibernate, является проблема управления сущностями во время выполнения. Понятие “контекст сохранения” – это решение Hibernate для этой проблемы. Контекст сохранения можно рассматривать как контейнер или кэш первого уровня для всех объектов, которые вы загрузили или сохранили в базе данных во время сеанса.

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

В режиме гибернации контекст сохранения представлен org.hibernate.Сеанс экземпляр. Для JPA это javax.постоянство.EntityManager . Когда мы используем Hibernate в качестве поставщика JPA и работаем через EntityManager интерфейс, реализация этого интерфейса в основном охватывает базовый Сеанс объект. Однако Hibernate Session предоставляет более богатый интерфейс с большим количеством возможностей, поэтому иногда полезно работать с Сессией напрямую .

2.2. Состояния экземпляров сущностей

Любой экземпляр сущности в вашем приложении отображается в одном из трех основных состояний в контексте Сеанса сохранения:

  • переходный — этот экземпляр не присоединен и никогда не был присоединен к Сеансу ; у этого экземпляра нет соответствующих строк в базе данных; обычно это просто новый объект, который вы создали для сохранения в базе данных;
  • постоянный — этот экземпляр связан с уникальным Сеансом объектом; при сбросе Сеанса в базу данных эта сущность гарантированно будет иметь соответствующую согласованную запись в базе данных;
  • отсоединенный — этот экземпляр когда-то был присоединен к Сеансупостоянном состоянии), но теперь это не так; экземпляр переходит в это состояние, если вы удаляете его из контекста, очищаете или закрываете сеанс или подвергаете экземпляр процессу сериализации/десериализации.

Вот упрощенная диаграмма состояний с комментариями к Сеансу методам, которые обеспечивают переходы состояний.

2016-07-11_13-38-11

Когда экземпляр сущности находится в постоянном состоянии, все изменения, которые вы вносите в сопоставленные поля этого экземпляра, будут применены к соответствующим записям и полям базы данных после очистки Сеанса . постоянный экземпляр можно рассматривать как “онлайн”, в то время как отсоединенный экземпляр перешел в “автономный режим” и не отслеживается на предмет изменений.

Это означает, что при изменении полей постоянного объекта вам не нужно вызывать сохранять , обновлять или любой из этих методов, чтобы внести эти изменения в базу данных: все, что вам нужно, это зафиксировать транзакцию, или сбросить или закрыть сеанс, когда вы закончите с этим.

2.3. Соответствие спецификации JPA

Hibernate был самой успешной реализацией Java ORM. Неудивительно, что на спецификацию Java persistence API (JPA) сильно повлиял API Hibernate. К сожалению, было также много различий: некоторые серьезные, некоторые более тонкие.

Чтобы действовать в качестве реализации стандарта JPA, API-интерфейсы Hibernate должны были быть пересмотрены. В интерфейс сеанса было добавлено несколько методов, соответствующих интерфейсу EntityManager. Эти методы служат той же цели, что и “оригинальные” методы, но соответствуют спецификации и, следовательно, имеют некоторые отличия.

3. Различия между Операциями

Важно с самого начала понять, что все методы ( persist , save , update , merge , saveOrUpdate ) не сразу приводят к соответствующим операторам SQL UPDATE или INSERT . Фактическое сохранение данных в базе данных происходит при совершении транзакции или сбросе Сеанса .

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

В качестве примера сущности мы будем использовать простую сопоставленную с аннотацией сущность Человек :

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    // ... getters and setters

}

3.1. Упорствовать

Метод persist предназначен для добавления нового экземпляра сущности в контекст сохранения, т. е. Для перехода экземпляра из переходного состояния в постоянное состояние.

Мы обычно вызываем его, когда хотим добавить запись в базу данных (сохранить экземпляр сущности).:

Person person = new Person();
person.setName("John");
session.persist(person);

Что происходит после вызова метода persist ? Человек объект перешел из переходного в постоянное состояние. Объект сейчас находится в контексте сохранения, но еще не сохранен в базе данных. Генерация инструкций INSERT будет происходить только при совершении транзакции, сбросе или закрытии сеанса.

Обратите внимание, что метод persist имеет тип возврата void . Он воздействует на переданный объект “на месте”, изменяя его состояние. Переменная person ссылается на фактический сохраняемый объект.

Этот метод является более поздним дополнением к интерфейсу сеанса. Основной отличительной особенностью этого метода является то, что он соответствует спецификации JSR-220 (стойкость EJB). Семантика этого метода строго определена в спецификации, которая в основном гласит, что:

  • переходный экземпляр становится постоянным (и операция каскадируется во все свои отношения с каскад=СОХРАНЯЕТСЯ или каскад=ВСЕ ),
  • если экземпляр уже постоянный , то этот вызов не имеет никакого эффекта для данного конкретного экземпляра (но он все равно каскадируется в свои отношения с каскад=СОХРАНЯЕТСЯ или каскад=ВСЕ ),
  • если экземпляр отсоединен , следует ожидать исключения либо при вызове этого метода, либо при фиксации или сбросе сеанса.

Обратите внимание, что здесь нет ничего, что касалось бы идентификатора экземпляра. В спецификации не указано, что идентификатор будет сгенерирован сразу, независимо от стратегии создания идентификатора. Спецификация для метода persist позволяет реализации выдавать инструкции для генерации идентификатора при фиксации или сбросе, и идентификатор не гарантируется, что он будет ненулевым после вызова этого метода, поэтому вам не следует полагаться на него.

Вы можете вызвать этот метод на уже постоянном экземпляре, и ничего не произойдет. Но если вы попытаетесь сохранить отсоединенный экземпляр, реализация обязательно выдаст исключение. В следующем примере мы сохраняем сущность, удаляем ее из контекста , чтобы она стала отделенной , а затем пытаемся сохранить снова. Второй вызов session.persist() вызывает исключение, поэтому следующий код не будет работать:

Person person = new Person();
person.setName("John");
session.persist(person);

session.evict(person);

session.persist(person); // PersistenceException!

3.2. Сохранить

Метод save является “оригинальным” методом гибернации, который не соответствует спецификации JPA.

Его цель в основном та же , что и persist , но у него разные детали реализации. В документации для этого метода строго указано, что он сохраняет экземпляр, “сначала присваивая сгенерированный идентификатор”. Метод гарантированно вернет Сериализуемое значение этого идентификатора.

Person person = new Person();
person.setName("John");
Long id = (Long) session.save(person);

Эффект сохранения уже сохраненного экземпляра такой же, как и при использовании persist . Разница возникает, когда вы пытаетесь сохранить отсоединенный экземпляр:

Person person = new Person();
person.setName("John");
Long id1 = (Long) session.save(person);

session.evict(person);
Long id2 = (Long) session.save(person);

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

3.3. Слияние

Основное намерение метода merge состоит в том, чтобы обновить постоянный экземпляр сущности новыми значениями полей из отсоединенного экземпляра сущности.

Например, предположим, что у вас есть интерфейс RESTful с методом извлечения JSON-сериализованного объекта по его идентификатору вызывающему абоненту и методом, который получает обновленную версию этого объекта от вызывающего абонента. Сущность, прошедшая через такую сериализацию/десериализацию, появится в отделенном состоянии.

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

  • находит экземпляр сущности по идентификатору, взятому из переданного объекта (извлекается либо существующий экземпляр сущности из контекста сохранения, либо новый экземпляр, загруженный из базы данных);
  • копирует поля из переданного объекта в этот экземпляр;
  • возвращает недавно обновленный экземпляр.

В следующем примере мы удаляем (отсоединяем) сохраненную сущность из контекста, изменяем поле имя , а затем объединяем | отделенную сущность.

Person person = new Person(); 
person.setName("John"); 
session.save(person);

session.evict(person);
person.setName("Mary");

Person mergedPerson = (Person) session.merge(person);

Обратите внимание, что метод merge возвращает объект — это объект mergedPerson , который был загружен в контекст сохранения и обновлен, а не объект person , который вы передали в качестве аргумента. Это два разных объекта, и объект person обычно необходимо отбросить (в любом случае, не рассчитывайте, что он будет привязан к контексту сохранения).

Как и в случае с методом persist , метод merge указан в JSR-220, чтобы иметь определенную семантику, на которую вы можете положиться:

  • если сущность отделена , она копируется на существующую постоянную сущность;
  • если сущность временная , она копируется на вновь созданную постоянную сущность;
  • эта операция каскадируется для всех отношений с каскад=СЛИЯНИЕ или каскад=ВСЕ отображение;
  • если сущность является постоянной , то этот вызов метода не влияет на нее (но каскадирование все равно происходит).

3.4. Обновление

Как и в случае persist и save , метод update является “оригинальным” методом гибернации, который присутствовал задолго до добавления метода merge . Его семантика отличается в нескольких ключевых моментах:

  • он действует на переданный объект (его возвращаемый тип void ); метод update переводит переданный объект из отсоединенного в постоянное состояние;
  • этот метод создает исключение, если вы передаете ему переходную сущность.

В следующем примере мы сохраняем объект, затем удаляем (отделяем) его от контекста, затем меняем его имя и вызываем обновление . Обратите внимание, что мы не помещаем результат операции update в отдельную переменную, потому что обновление происходит на самом объекте person . По сути, мы повторно подключаем существующий экземпляр сущности к контексту сохранения — то, что спецификация JPA не позволяет нам сделать.

Person person = new Person();
person.setName("John");
session.save(person);
session.evict(person);

person.setName("Mary");
session.update(person);

Попытка вызвать обновление в переходном экземпляре приведет к исключению. Следующее не сработает:

Person person = new Person();
person.setName("John");
session.update(person); // PersistenceException!

3.5. Сохранить или обновить

Этот метод отображается только в API Hibernate и не имеет своего стандартизированного аналога. Аналогично update , он также может использоваться для повторного подключения экземпляров.

На самом деле, внутренний класс DefaultUpdateEventListener , который обрабатывает метод update , является подклассом DefaultSaveOrUpdateListener , просто переопределяющим некоторые функции. Основное отличие метода saveOrUpdate заключается в том, что он не создает исключения при применении к переходному экземпляру; вместо этого он делает этот переходный экземпляр постоянным . Следующий код сохранит вновь созданный экземпляр Person :

Person person = new Person();
person.setName("John");
session.saveOrUpdate(person);

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

4. Что использовать?

Если у вас нет каких-либо особых требований, как правило, вы должны придерживаться методов persist и merge , поскольку они стандартизированы и гарантированно соответствуют спецификации JPA.

Они также переносимы на случай, если вы решите переключиться на другого поставщика сохраняемости, но иногда они могут оказаться не такими полезными, как “оригинальные” методы гибернации, сохранить , обновить и Сохранить обновление .

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

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

Исходный код статьи доступен на GitHub .