Хибернация: запазване, персистиране, актуализиране, обединяване, saveOrUpdate

1. Въведение

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

Това не е въведение в Hibernate и вече трябва да знаете основите на конфигурацията, обектно-релационното картографиране и работата с екземпляри на обекти. За уводна статия за Hibernate, посетете нашия урок за Hibernate 4 с Spring.

2. Сесията като изпълнение на контекста на постоянство

Интерфейсът на сесията има няколко метода, които в крайна сметка водят до запазване на данни в базата данни: persist , save , update , merge , saveOrUpdate . За да разберем разликата между тези методи, първо трябва да обсъдим целта на сесията като контекст на постоянство и разликата между състоянията на екземплярите на обекти по отношение на сесията .

Трябва също така да разберем историята на развитието на Hibernate, което доведе до някои частично дублирани методи на API.

2.1. Управление на екземпляри на обекти

Освен самото обектно-релационно картографиране, един от проблемите, които Hibernate трябваше да реши, е проблемът с управлението на обекти по време на изпълнение. Понятието „контекст на постоянство“ е решението на Hibernate за този проблем. Контекстът на постоянство може да се разглежда като контейнер или кеш от първо ниво за всички обекти, които сте заредили или записали в база данни по време на сесия.

Сесията е логическа транзакция, чиито граници се определят от бизнес логиката на вашето приложение. Когато работите с базата данни чрез контекст на постоянство и всички ваши екземпляри на обекти са прикрепени към този контекст, винаги трябва да имате по един екземпляр на обект за всеки запис в базата данни, с който сте взаимодействали по време на сесията.

В режим на хибернация контекстът на постоянство е представен от екземпляр org.hibernate.Session . За JPA това е javax.persistence.EntityManager . Когато използваме Hibernate като JPA доставчик и работим чрез интерфейс EntityManager , изпълнението на този интерфейс обгръща основно основния обект Session . Въпреки това Hibernate Session предоставя по-богат интерфейс с повече възможности, така че понякога е полезно да работите директно със Session .

2.2. Състояния на юридически лица

Всеки екземпляр на обект във вашето приложение се появява в едно от трите основни състояния във връзка с контекста на трайност на сесията :

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

Ето опростена диаграма на състоянието с коментари за методите на сесията , които правят преходите на състоянието.

Когато екземплярът на обекта е в постоянно състояние, всички промени, които правите в картографираните полета на този екземпляр, ще бъдат приложени към съответните записи и полета на базата данни при изчистване на сесията . Най- устойчиви инстанция може да се мисли като "онлайн", докато самостоятелна инстанция е отишло "офлайн" и не се следи за промени.

Това означава, че когато сменяте полета на постоянен обект, не е нужно да извиквате save , update или някой от тези методи, за да получите тези промени в базата данни: всичко, от което се нуждаете, е да ангажирате транзакцията или да изчистите или затворите сесията , когато сте готови с него.

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

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

За да действат като изпълнение на стандарта JPA, Hibernate API трябваше да бъдат ревизирани. Няколко метода бяха добавени към интерфейса на сесията, за да съответстват на интерфейса на EntityManager. Тези методи служат на същата цел като „оригиналните“ методи, но отговарят на спецификацията и по този начин имат някои разлики.

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

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

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

Като примерен обект ще използваме прост обект, съотнесен към анотацията Person :

@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);

What happens after the persist method is called? The person object has transitioned from transient to persistent state. The object is in the persistence context now, but not yet saved to the database. The generation of INSERT statements will occur only upon commiting the transaction, flushing or closing the session.

Notice that the persist method has void return type. It operates on the passed object “in place”, changing its state. The person variable references the actual persisted object.

This method is a later addition to the Session interface. The main differentiating feature of this method is that it conforms to the JSR-220 specification (EJB persistence). The semantics of this method is strictly defined in the specification, which basically states, that:

  • a transient instance becomes persistent (and the operation cascades to all of its relations with cascade=PERSIST or cascade=ALL),
  • if an instance is already persistent, then this call has no effect for this particular instance (but it still cascades to its relations with cascade=PERSIST or cascade=ALL),
  • if an instance is detached, you should expect an exception, either upon calling this method, or upon committing or flushing the session.

Notice that there is nothing here that concerns the identifier of an instance. The spec does not state that the id will be generated right away, regardless of the id generation strategy. The specification for the persist method allows the implementation to issue statements for generating id on commit or flush, and the id is not guaranteed to be non-null after calling this method, so you should not rely upon it.

You may call this method on an already persistent instance, and nothing happens. But if you try to persist a detached instance, the implementation is bound to throw an exception. In the following example we persist the entity, evict it from the context so it becomes detached, and then try to persist again. The second call to session.persist() causes an exception, so the following code will not work:

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

3.2. Save

The save method is an “original” Hibernate method that does not conform to the JPA specification.

Its purpose is basically the same as persist, but it has different implementation details. The documentation for this method strictly states that it persists the instance, “first assigning a generated identifier”. The method is guaranteed to return the Serializable value of this identifier.

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

The effect of saving an already persisted instance is the same as with persist. Difference comes when you try to save a detached instance:

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

The id2 variable will differ from id1. The call of save on a detached instance creates a new persistent instance and assigns it a new identifier, which results in a duplicate record in a database upon committing or flushing.

3.3. Merge

The main intention of the merge method is to update a persistent entity instance with new field values from a detached entity instance.

For instance, suppose you have a RESTful interface with a method for retrieving an JSON-serialized object by its id to the caller and a method that receives an updated version of this object from the caller. An entity that passed through such serialization/deserialization will appear in a detached state.

After deserializing this entity instance, you need to get a persistent entity instance from a persistence context and update its fields with new values from this detached instance. So the merge method does exactly that:

  • finds an entity instance by id taken from the passed object (either an existing entity instance from the persistence context is retrieved, or a new instance loaded from the database);
  • copies fields from the passed object to this instance;
  • returns newly updated instance.

In the following example we evict (detach) the saved entity from context, change the name field, and then merge the detached entity.

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

Note that the merge method returns an object — it is the mergedPerson object that was loaded into persistence context and updated, not the person object that you passed as an argument. Those are two different objects, and the person object usually needs to be discarded (anyway, don't count on it being attached to persistence context).

As with persist method, the merge method is specified by JSR-220 to have certain semantics that you can rely upon:

  • if the entity is detached, it is copied upon an existing persistent entity;
  • if the entity is transient, it is copied upon a newly created persistent entity;
  • this operation cascades for all relations with cascade=MERGE or cascade=ALL mapping;
  • if the entity is persistent, then this method call does not have effect on it (but the cascading still takes place).

3.4. Update

As with persist and save, the update method is an “original” Hibernate method that was present long before the merge method was added. Its semantics differs in several key points:

  • it acts upon passed object (its return type is void); the update method transitions the passed object from detached to persistent state;
  • this method throws an exception if you pass it a transient entity.

In the following example we save the object, then evict (detach) it from the context, then change its name and call update. Notice that we don't put the result of the update operation in a separate variable, because the update takes place on the person object itself. Basically we're reattaching the existing entity instance to the persistence context — something the JPA specification does not allow us to do.

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

Trying to call update on a transient instance will result in an exception. The following will not work:

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

3.5. SaveOrUpdate

This method appears only in the Hibernate API and does not have its standardized counterpart. Similar to update, it also may be used for reattaching instances.

Actually, the internal DefaultUpdateEventListener class that processes the update method is a subclass of DefaultSaveOrUpdateListener, just overriding some functionality. The main difference of saveOrUpdate method is that it does not throw exception when applied to a transient instance; instead, it makes this transient instance persistent. The following code will persist a newly created instance of Person:

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

You may think of this method as a universal tool for making an object persistent regardless of its state wether it is transient or detached.

4. What to Use?

If you don't have any special requirements, as a rule of thumb, you should stick to the persist and merge methods, because they are standardized and guaranteed to conform to the JPA specification.

They are also portable in case you decide to switch to another persistence provider, but they may sometimes appear not so useful as the “original” Hibernate methods, save, update and saveOrUpdate.

5. Conclusion

Обсъдихме целта на различните методи на Hibernate Session във връзка с управлението на постоянни обекти по време на изпълнение. Научихме как тези методи транзитират екземпляри на обекти през жизнения им цикъл и защо някои от тези методи имат дублирана функционалност.

Изходният код на статията е достъпен на GitHub.