Hibernation: enregistrer, conserver, mettre à jour, fusionner, enregistrer ou mettre à jour

1. Introduction

Dans cet article, nous discuterons des différences entre plusieurs méthodes de l' interface de session : enregistrer , conserver , mettre à jour , fusionner , enregistrerOrUpdate .

Ce n'est pas une introduction à Hibernate et vous devriez déjà connaître les bases de la configuration, du mappage objet-relationnel et de l'utilisation des instances d'entité. Pour un article d'introduction à Hibernate, visitez notre tutoriel sur Hibernate 4 avec Spring.

2. Session comme implémentation de contexte de persistance

L' interface de session a plusieurs méthodes qui aboutissent finalement à enregistrer des données dans la base de données: persister , enregistrer , mettre à jour , fusionner , enregistrerOrUpdate . Pour comprendre la différence entre ces méthodes, nous devons d'abord discuter de l'objectif de la session en tant que contexte de persistance et de la différence entre les états des instances d'entité par rapport à la session .

Nous devons également comprendre l'histoire du développement d'Hibernate qui a conduit à des méthodes d'API partiellement dupliquées.

2.1. Gestion des instances d'entité

Outre le mappage objet-relationnel lui-même, l'un des problèmes qu'Hibernate était censé résoudre est le problème de la gestion des entités pendant l'exécution. La notion de «contexte de persistance» est la solution d'Hibernate à ce problème. Le contexte de persistance peut être considéré comme un conteneur ou un cache de premier niveau pour tous les objets que vous avez chargés ou enregistrés dans une base de données au cours d'une session.

La session est une transaction logique, dont les limites sont définies par la logique métier de votre application. Lorsque vous travaillez avec la base de données via un contexte de persistance et que toutes vos instances d'entité sont attachées à ce contexte, vous devez toujours avoir une seule instance d'entité pour chaque enregistrement de base de données avec lequel vous avez interagi pendant la session.

Dans Hibernate, le contexte de persistance est représenté par l' instance org.hibernate.Session . Pour JPA, il s'agit du javax.persistence.EntityManager . Lorsque nous utilisons Hibernate en tant que fournisseur JPA et que nous opérons via l' interface EntityManager , l'implémentation de cette interface encapsule essentiellement l' objet Session sous-jacent . Cependant, Hibernate Session fournit une interface plus riche avec plus de possibilités, il est donc parfois utile de travailler directement avec Session .

2.2. États des instances d'entité

Toute instance d'entité de votre application apparaît dans l'un des trois états principaux par rapport au contexte de persistance de session :

  • transitoire - cette instance n'est pas, et n'a jamais été, attachée à une session ; cette instance n'a pas de lignes correspondantes dans la base de données; il s'agit généralement d'un nouvel objet que vous avez créé pour le sauvegarder dans la base de données;
  • persistant - cette instance est associée à un objet Session unique ; lors du vidage de la session dans la base de données, cette entité est garantie d'avoir un enregistrement cohérent correspondant dans la base de données;
  • détaché - cette instance était autrefois attachée à une session (dans un état persistant ), mais maintenant elle ne l'est plus; une instance entre dans cet état si vous l'expulsez du contexte, effacez ou fermez la session ou soumettez l'instance à un processus de sérialisation / désérialisation.

Voici un diagramme d'état simplifié avec des commentaires sur les méthodes de session qui permettent les transitions d'état.

Lorsque l'instance d'entité est dans l' état persistant , toutes les modifications que vous apportez aux champs mappés de cette instance seront appliquées aux enregistrements et aux champs de base de données correspondants lors du vidage de la session . L' instance persistante peut être considérée comme «en ligne», tandis que l' instance détachée est passée «hors ligne» et n'est pas surveillée pour les modifications.

Cela signifie que lorsque vous modifiez les champs d'un objet persistant , vous n'avez pas besoin d'appeler save , update ou l'une de ces méthodes pour obtenir ces modifications dans la base de données: tout ce dont vous avez besoin est de valider la transaction, ou de vider ou de fermer la session , lorsque vous en avez terminé.

2.3. Conformité à la spécification JPA

Hibernate a été la mise en œuvre Java ORM la plus réussie. Pas étonnant que la spécification de l'API de persistance Java (JPA) ait été fortement influencée par l'API Hibernate. Malheureusement, il y avait aussi de nombreuses différences: certaines majeures, d'autres plus subtiles.

Pour agir comme une implémentation de la norme JPA, les API Hibernate ont dû être révisées. Plusieurs méthodes ont été ajoutées à l'interface de session pour correspondre à l'interface EntityManager. Ces méthodes ont le même objectif que les méthodes «originales», mais sont conformes à la spécification et présentent donc quelques différences.

3. Différences entre les opérations

Il est important de comprendre dès le début que toutes les méthodes ( persist , save , update , merge , saveOrUpdate ) n'entraînent pas immédiatement les instructions SQL UPDATE ou INSERT correspondantes . L'enregistrement réel des données dans la base de données se produit lors de la validation de la transaction ou du vidage de la session .

Les méthodes mentionnées gèrent essentiellement l'état des instances d'entité en les faisant passer entre différents états tout au long du cycle de vie.

À titre d'exemple d'entité, nous utiliserons une simple entité associée à une annotation Person :

@Entity public class Person { @Id @GeneratedValue private Long id; private String name; // ... getters and setters }

3.1. Persister

La méthode persist est destinée à ajouter une nouvelle instance d'entité au contexte de persistance, c'est-à-dire à faire passer une instance de l'état transitoire à l' état persistant .

Nous l'appelons généralement lorsque nous voulons ajouter un enregistrement à la base de données (conserver une instance d'entité):

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

Que se passe-t-il après l' appel de la méthode persist ? L' objet personne est passé de l'état transitoire à l' état persistant . L'objet est maintenant dans le contexte de persistance, mais pas encore enregistré dans la base de données. La génération d' instructions INSERT se produira uniquement lors de la validation de la transaction, du vidage ou de la fermeture de la session.

Notez que la méthode persist a un type de retour void . Il opère sur l'objet passé «en place», en changeant son état. La variable person fait référence à l'objet persistant réel.

Cette méthode est un ajout ultérieur à l'interface de session. La principale caractéristique de différenciation de cette méthode est qu'elle est conforme à la spécification JSR-220 (persistance EJB). La sémantique de cette méthode est strictement définie dans la spécification, qui stipule essentiellement que:

  • une instance transitoire devient persistante (et l'opération tombe en cascade dans toutes ses relations avec cascade = PERSIST ou cascade = ALL ),
  • si une instance est déjà persistante , alors cet appel n'a aucun effet pour cette instance particulière (mais il cascades toujours vers ses relations avec cascade = PERSIST ou 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

Nous avons discuté de l'objectif des différentes méthodes Hibernate Session en ce qui concerne la gestion des entités persistantes en runtime. Nous avons appris comment ces méthodes transforment les instances d'entités tout au long de leur cycle de vie et pourquoi certaines de ces méthodes ont des fonctionnalités dupliquées.

Le code source de l'article est disponible sur GitHub.