Hibernate: сохранить, сохранить, обновить, объединить, saveOrUpdate

Hibernate: сохранить, сохранить, обновить, объединить, saveOrUpdate

1. Вступление

В этой статье мы обсудим различия между несколькими методами интерфейсаSession:save,persist,update,merge,saveOrUpdate.

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

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

Удаление объектов с помощью Hibernate

Краткое руководство по удалению сущности в Hibernate.

Read more

Хранимые процедуры с Hibernate

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

Read more

Обзор идентификаторов в Hibernate

Узнайте, как отобразить идентификаторы сущностей с помощью Hibernate.

Read more

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

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

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

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

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

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

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

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

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

  • transient - этот экземпляр не был и никогда не был присоединен кSession; у этого экземпляра нет соответствующих строк в базе данных; обычно это просто новый объект, который вы создали для сохранения в базе данных;

  • persistent - этот экземпляр связан с уникальным объектомSession; после сбросаSession в базу данных этот объект гарантированно будет иметь соответствующую согласованную запись в базе данных;

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

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

2016-07-11_13-38-11

Когда экземпляр объекта находится в состоянииpersistent, все изменения, которые вы вносите в сопоставленные поля этого экземпляра, будут применены к соответствующим записям и полям базы данных после сбросаSession. Экземплярpersistent можно рассматривать как «подключенный», тогда как экземплярdetached перешел в «автономный режим» и не отслеживается на предмет изменений.

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

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

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

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

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

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

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

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

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    // ... getters and setters

}

3.1. Persistс

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

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

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

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

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

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

  • экземплярtransient становитсяpersistent (и операция каскадируется на все его отношения сcascade=PERSIST илиcascade=ALL),

  • если экземпляр ужеpersistent, то этот вызов не имеет никакого эффекта для этого конкретного экземпляра (но он по-прежнему каскадирует его отношения сcascade=PERSIST илиcascade=ALL),

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

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

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

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

session.evict(person);

session.persist(person); // PersistenceException!

3.2. Saveс

Методsave - это «оригинальный» метод Hibernate, который не соответствует спецификации JPA.

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

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

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

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

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

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

3.3. Mergeс

Основное назначение методаmerge - обновить экземпляр объектаpersistent новыми значениями полей из экземпляра объектаdetached.

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

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

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

  • копирует поля из переданного объекта в этот экземпляр;

  • возвращает обновленный экземпляр

В следующем примере мыevict (отсоединяем) сохраненную сущность от контекста, меняем полеname, а затемmerge сущностьdetached.

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, чтобы иметь определенную семантику, на которую вы можете положиться:

  • если объектdetached, он копируется на существующий объектpersistent;

  • если объект -transient, он копируется на вновь созданный объектpersistent;

  • эта операция каскадируется для всех отношений с отображениемcascade=MERGE илиcascade=ALL;

  • если сущностьpersistent, то этот вызов метода не влияет на нее (но каскадирование все еще происходит).

3.4. Updateс

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

  • он действует на переданный объект (его возвращаемый типvoid); методupdate переводит переданный объект из состоянияdetached в состояниеpersistent;

  • этот метод вызывает исключение, если вы передаете ему объектtransient.

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

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

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

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

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

3.5. SaveOrUpdateс

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

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

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

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

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

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

Они также переносимы на случай, если вы решите переключиться на другого поставщика сохраняемости, но иногда они могут показаться не такими полезными, как «оригинальные» методы Hibernate,save,update иsaveOrUpdate.

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

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

Исходный код статьиavailable on GitHub.