1. введение
В этой статье мы обсудим различия между несколькими методами интерфейса Сеанса : сохранить , сохранить , обновить , объединить , Сохранить обновление .
Это не введение в режим гибернации, и вы уже должны знать основы конфигурации, объектно-реляционного сопоставления и работы с экземплярами сущностей. Для получения вводной статьи о гибернации посетите наш учебник по гибернации 4 с помощью Spring .
Дальнейшее чтение:
Удаление объектов с помощью режима гибернации
Хранимые процедуры с режимом гибернации
Обзор идентификаторов в режиме гибернации/JPA
2. Сессия как реализация контекста сохранения
Интерфейс Session имеет несколько методов, которые в конечном итоге приводят к сохранению данных в базе данных: сохранение , сохранение , обновление , слияние , Сохранение | обновление|/. Чтобы понять разницу между этими методами, мы должны сначала обсудить цель Сеанса как контекста сохранения и разницу между состояниями экземпляров сущностей по отношению к Сеансу .
Мы также должны понять историю разработки Hibernate, которая привела к некоторым частично дублированным методам API.
2.1. Управление Экземплярами Сущностей
Помимо самого объектно-реляционного отображения, одной из проблем, для решения которой был предназначен Hibernate, является проблема управления сущностями во время выполнения. Понятие “контекст сохранения” – это решение Hibernate для этой проблемы. Контекст сохранения можно рассматривать как контейнер или кэш первого уровня для всех объектов, которые вы загрузили или сохранили в базе данных во время сеанса.
Сеанс представляет собой логическую транзакцию, границы которой определяются бизнес-логикой вашего приложения. Когда вы работаете с базой данных в контексте сохранения и все ваши экземпляры сущностей подключены к этому контексту, у вас всегда должен быть один экземпляр сущности для каждой записи базы данных, с которой вы взаимодействовали во время сеанса.
В режиме гибернации контекст сохранения представлен org.hibernate.Сеанс экземпляр. Для JPA это javax.постоянство.EntityManager . Когда мы используем Hibernate в качестве поставщика JPA и работаем через EntityManager интерфейс, реализация этого интерфейса в основном охватывает базовый Сеанс объект. Однако Hibernate Session предоставляет более богатый интерфейс с большим количеством возможностей, поэтому иногда полезно работать с Сессией напрямую .
2.2. Состояния экземпляров сущностей
Любой экземпляр сущности в вашем приложении отображается в одном из трех основных состояний в контексте Сеанса сохранения:
- переходный — этот экземпляр не присоединен и никогда не был присоединен к Сеансу ; у этого экземпляра нет соответствующих строк в базе данных; обычно это просто новый объект, который вы создали для сохранения в базе данных;
- постоянный — этот экземпляр связан с уникальным Сеансом объектом; при сбросе Сеанса в базу данных эта сущность гарантированно будет иметь соответствующую согласованную запись в базе данных;
- отсоединенный — этот экземпляр когда-то был присоединен к Сеансу (в постоянном состоянии), но теперь это не так; экземпляр переходит в это состояние, если вы удаляете его из контекста, очищаете или закрываете сеанс или подвергаете экземпляр процессу сериализации/десериализации.
Вот упрощенная диаграмма состояний с комментариями к Сеансу методам, которые обеспечивают переходы состояний.
Когда экземпляр сущности находится в постоянном состоянии, все изменения, которые вы вносите в сопоставленные поля этого экземпляра, будут применены к соответствующим записям и полям базы данных после очистки Сеанса . постоянный экземпляр можно рассматривать как “онлайн”, в то время как отсоединенный экземпляр перешел в “автономный режим” и не отслеживается на предмет изменений.
Это означает, что при изменении полей постоянного объекта вам не нужно вызывать сохранять , обновлять или любой из этих методов, чтобы внести эти изменения в базу данных: все, что вам нужно, это зафиксировать транзакцию, или сбросить или закрыть сеанс, когда вы закончите с этим.
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 .