Hibernate: sauvegarde, persistance, mise à jour, fusion, saveOrUpdate

Hibernate: sauvegarder, persister, mettre à jour, fusionner, saveOrUpdate

1. introduction

Dans cet article, nous discuterons des différences entre plusieurs méthodes de l'interfaceSession:save,persist,update,merge,saveOrUpdate.

Il ne s'agit pas d'une introduction à Hibernate et vous devez déjà connaître les bases de la configuration, du mappage objet-relationnel et de l'utilisation d'instances d'entités. Pour un article d'introduction à Hibernate, visitez notre tutoriel surHibernate 4 with Spring.

Lectures complémentaires:

Suppression d'objets avec Hibernate

Guide rapide pour la suppression d'une entité dans Hibernate.

Read more

Procédures stockées avec Hibernate

Cet article explique brièvement comment appeler les procédures de magasin d’Hibernate.

Read more

Présentation des identifiants dans Hibernate

Apprenez à mapper les identificateurs d'entité avec Hibernate.

Read more

2. Session comme implémentation de contexte de persistance

L'interfaceSession dispose de plusieurs méthodes qui aboutissent finalement à l'enregistrement des données dans la base de données:persist,save,update,merge,saveOrUpdate. Pour comprendre la différence entre ces méthodes, nous devons d'abord discuter de l'objectif desSession en tant que contexte de persistance et de la différence entre les états des instances d'entité par rapport auxSession.

Nous devons également comprendre l'historique 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 était 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 utilisez la base de données dans un contexte de persistance et que toutes vos instances d'entité sont attachées à ce contexte, vous devez toujours disposer d'une instance d'entité unique pour chaque enregistrement de base de données avec lequel vous avez interagi au cours de la session.

Dans Hibernate, le contexte de persistance est représenté par l'instanceorg.hibernate.Session. Pour JPA, ce sont lesjavax.persistence.EntityManager. Lorsque nous utilisons Hibernate comme fournisseur JPA et que nous opérons via l'interfaceEntityManager, l'implémentation de cette interface encapsule en gros l'objetSession sous-jacent. Cependant, HibernateSession fournit une interface plus riche avec plus de possibilités, il est donc parfois utile de travailler avecSession directly.

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 persistanceSession:

  • transient - cette instance n'est pas, et n'a jamais été, attachée à unSession; 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 l’enregistrer dans la base de données;

  • persistent - cette instance est associée à un objetSession unique; lors du vidage desSession dans la base de données, cette entité est garantie d'avoir un enregistrement cohérent correspondant dans la base de données;

  • detached - cette instance était autrefois attachée à unSession (dans un étatpersistent), mais maintenant ce n'est pas le cas; une instance entre dans cet état si vous l'expultez 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éthodesSession qui permettent les transitions d'état.

2016-07-11_13-38-11

Lorsque l'instance d'entité est à l'étatpersistent, 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 desSession. L'instancepersistent peut être considérée comme «en ligne», alors que l'instancedetached est passée «hors ligne» et n'est pas surveillée pour les changements.

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

2.3. Conformité à la spécification JPA

Hibernate a été l'implémentation 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 beaucoup de différences: certaines majeures, d'autres plus subtiles.

Pour fonctionner comme une implémentation de la norme JPA, les API Hibernate devaient ê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 certaines 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'aboutissent pas immédiatement instructions SQLUPDATE ouINSERT correspondantes. La sauvegarde réelle des données dans la base de données se produit lors de la validation de la transaction ou du vidage desSession.

Les méthodes mentionnées gèrent fondamentalement l’état des instances d’entité en les faisant passer d’un état à l’autre au cours du cycle de vie.

Comme exemple d'entité, nous utiliserons une simple entité mappée d'annotationsPerson:

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    // ... getters and setters

}

3.1. Persist

La méthodepersist est destinée à ajouter une nouvelle instance d'entité au contexte de persistance, c'est-à-dire transition d'une instance de l'état transitoire à l'étatpersistent.

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éthodepersist? L'objetperson est passé de l'étattransient à l'étatpersistent. L'objet est maintenant dans le contexte de persistance, mais n'est pas encore enregistré dans la base de données. La génération des instructionsINSERT se produira uniquement lors de la validation de la transaction, du vidage ou de la fermeture de la session.

Notez que la méthodepersist a le type de retourvoid. Il opère sur l'objet passé «en place», en changeant son état. La variableperson 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 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 fondamentalement que:

  • une instance detransient devientpersistent (et l'opération tombe en cascade dans toutes ses relations aveccascade=PERSIST oucascade=ALL),

  • si une instance est déjàpersistent, alors cet appel n'a aucun effet pour cette instance particulière (mais il continue en cascade vers ses relations aveccascade=PERSIST oucascade=ALL),

  • si une instance estdetached, vous devez vous attendre à une exception, soit lors de l'appel de cette méthode, soit lors de la validation ou du vidage de la session.

Notez qu'il n'y a rien ici qui concerne l'identifiant d'une instance. La spécification n'indique pas que l'identifiant sera généré immédiatement, quelle que soit la stratégie de génération d'identifiant. La spécification de la méthodepersist permet à l'implémentation d'émettre des instructions pour générer l'id lors de la validation ou du vidage, et l'id n'est pas garanti non nul après l'appel de cette méthode, vous ne devez donc pas vous y fier.

Vous pouvez appeler cette méthode sur une instance déjàpersistent, et rien ne se passe. Mais si vous essayez de conserver une instancedetached, l'implémentation est obligée de lever une exception. Dans l'exemple suivant, nouspersist l'entité,evict elle du contexte pour qu'elle deviennedetached, puis essayons à nouveau depersist. Le deuxième appel àsession.persist() provoque une exception, donc le code suivant ne fonctionnera pas:

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

session.evict(person);

session.persist(person); // PersistenceException!

3.2. Save

La méthodesave est une méthode Hibernate «originale» qui n'est pas conforme à la spécification JPA.

Son objectif est fondamentalement le même que celui depersist, mais il a des détails d'implémentation différents. La documentation de cette méthode indique strictement qu'elle persiste dans l'instance, «attribuant d'abord un identifiant généré». Il est garanti que la méthode renvoie la valeurSerializable de cet identificateur.

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

L'effet de l'enregistrement d'une instance déjà persistante est le même qu'avecpersist. La différence survient lorsque vous essayez de sauvegarder une instance dedetached:

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

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

La variableid2 sera différente deid1. L'appel de save sur une instancedetached crée une nouvelle instancepersistent et lui affecte un nouvel identifiant, ce qui entraîne un enregistrement dupliqué dans une base de données lors de la validation ou du vidage.

3.3. Merge

L'intention principale de la méthodemerge est de mettre à jour une instance d'entitépersistent avec de nouvelles valeurs de champ à partir d'une instance d'entitédetached.

Par exemple, supposons que vous ayez une interface RESTful avec une méthode permettant d'extraire un objet sérialisé JSON de son identifiant vers l'appelant et une méthode recevant une version mise à jour de cet objet de l'appelant. Une entité qui est passée par une telle sérialisation / désérialisation apparaîtra dans un étatdetached.

Après avoir désérialisé cette instance d'entité, vous devez obtenir une instance d'entitépersistent à partir d'un contexte de persistance et mettre à jour ses champs avec les nouvelles valeurs de cette instancedetached. Donc, la méthodemerge fait exactement cela:

  • trouve une instance d'entité à l'aide de l'ID extrait de l'objet transmis (une instance d'instance existante du contexte de persistance est extraite ou une nouvelle instance chargée à partir de la base de données);

  • copie les champs de l'objet passé dans cette instance;

  • renvoie l'instance nouvellement mise à jour.

Dans l'exemple suivant, nousevict (détachons) l'entité enregistrée du contexte, modifions le champname, puismerge l'entitédetached.

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

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

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

Notez que la méthodemerge renvoie un objet - c'est l'objetmergedPerson qui a été chargé dans le contexte de persistance et mis à jour, pas l'objetperson que vous avez passé en argument. Ce sont deux objets différents, et l’objetperson doit généralement être ignoré (de toute façon, ne comptez pas sur son attachement au contexte de persistance).

Comme avec la méthodepersist, la méthodemerge est spécifiée par JSR-220 pour avoir certaines sémantiques sur lesquelles vous pouvez compter:

  • si l'entité estdetached, elle est copiée sur une entitépersistent existante;

  • si l'entité esttransient, elle est copiée sur une entitépersistent nouvellement créée;

  • cette opération cascades pour toutes les relations avec le mappagecascade=MERGE oucascade=ALL;

  • si l'entité estpersistent, alors cet appel de méthode n'a pas d'effet sur elle (mais la cascade a toujours lieu).

3.4. Update

Comme pourpersist etsave, la méthodeupdate est une méthode Hibernate «originale» qui était présente bien avant l'ajout de la méthodemerge. Sa sémantique diffère sur plusieurs points clés:

  • il agit sur l'objet passé (son type de retour estvoid); la méthodeupdate fait passer l'objet passé de l'étatdetached à l'étatpersistent;

  • cette méthode lève une exception si vous lui passez une entitétransient.

Dans l'exemple suivant, noussave l'objet, puisevict (le détachons) du contexte, puis changeons sesname et appelonsupdate. Notez que nous ne mettons pas le résultat de l’opérationupdate dans une variable séparée, car leupdate a lieu sur l’objetperson lui-même. Fondamentalement, nous rattacherons l'instance d'entité existante au contexte de persistance - ce que la spécification JPA ne nous permet pas de faire.

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

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

Tenter d'appelerupdate sur une instancetransient entraînera une exception. Ce qui suit ne fonctionnera pas:

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

3.5. SaveOrUpdate

Cette méthode n'apparaît que dans l'API Hibernate et n'a pas son équivalent normalisé. Similaire àupdate, il peut également être utilisé pour rattacher des instances.

En fait, la classe interneDefaultUpdateEventListener qui traite la méthodeupdate est une sous-classe deDefaultSaveOrUpdateListener, remplaçant juste certaines fonctionnalités. La principale différence de la méthodesaveOrUpdate est qu'elle ne lève pas d'exception lorsqu'elle est appliquée à une instancetransient; à la place, il rend cette instancetransientpersistent. Le code suivant conservera une instance nouvellement créée dePerson:

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

Vous pouvez considérer cette méthode comme un outil universel pour créer un objetpersistent quel que soit son état, qu'il s'agisse detransient oudetached.

4. Quoi utiliser?

Si vous n’avez pas d’exigences particulières, en règle générale, vous devez vous en tenir aux méthodespersist etmerge, car elles sont standardisées et garanties conformes à la spécification JPA.

Ils sont également portables au cas où vous décideriez de passer à un autre fournisseur de persistance, mais ils peuvent parfois sembler moins utiles que les méthodes Hibernate «originales»,save,update etsaveOrUpdate.

5. Conclusion

Nous avons discuté de l’objectif de différentes méthodes Hibernate Session en ce qui concerne la gestion des entités persistantes au moment de l’exécution. Nous avons appris comment ces méthodes transposent 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 estavailable on GitHub.