JPA et Hibernate fournissent différentes méthodes pour conserver les nouvelles entités et mettre à jour les entités existantes. Vous pouvez choisir entre les méthodes de sauvegarde et de mise à jour de JPA persist et merge et Hibernate.
Il semble qu’il existe 2 paires de 2 méthodes qui font de même. Vous pouvez utiliser les méthodes conserver et enregistrer pour stocker une nouvelle entité et les méthodes fusionner et mettre à jour pour stocker les modifications d’une entité détachée dans la base de données. C’est pourquoi beaucoup de développeurs se demandent laquelle de ces méthodes ils devraient utiliser. Examinons de plus près les détails et les petites différences de ces méthodes.
Un merci spécial à Steve Ebersole (développeur principal – ORM Hibernate) qui a fourni ses commentaires et d’excellentes idées sur certains des détails d’implémentation cachés d’Hibernate!
- Transitions d’état d’entité
- Persistance d’une Nouvelle Entité À l’aide de persist Ou save
- Spécification vs. API propriétaire
- Types De Retour Et Exécution D’Instructions SQL
- Non généré
- Généré avec la stratégie d’identité
- Généré avec la stratégie DE SÉQUENCE
- Généré avec la stratégie de TABLE
- Lequel choisir ?
- Mise à jour d’une entité détachée
- Méthode de fusion de JPA
- Méthode de mise à jour d’Hibernate
- Lequel choisir ?
- Mise à jour d’une entité gérée
Transitions d’état d’entité
Avant d’entrer dans les détails de ces 4 méthodes, je dois vous donner une introduction rapide aux états de cycle de vie d’entité de JPA.
Si une entité est attachée au contexte de persistance actuel, l’état du cycle de vie est géré. Cela signifie qu’il est mappé à un enregistrement de base de données. Votre fournisseur de persistance génère les instructions SQL INSERT et UPDATE requises pour propager toutes les modifications. Une entité gérée est également stockée dans le cache de 1er niveau.
Lorsque vous créez une nouvelle entité, elle est dans l’état transitoire. Il reste dans cet état jusqu’à ce que vous l’attachiez au contexte de persistance actuel. Je vais vous montrer comment vous pouvez le faire avec la méthode de sauvegarde persist et Hibernate de JPA, dans la section suivante. Tant qu’une entité est dans l’état transitoire, elle n’est pas mappée à un enregistrement de base de données et n’est gérée par aucun contexte de persistance.
Les entités dans l’état de cycle de vie détaché ne sont plus gérées par le contexte de persistance. Cela peut être le cas parce que vous avez fermé le contexte de persistance ou que vous avez explicitement détaché l’entité du contexte actuel. Je vais entrer dans plus de détails sur la façon dont vous pouvez rattacher ces entités avec les méthodes de fusion et de mise à jour de JPA dans une partie ultérieure de cet article.
Et le dernier état de cycle de vie est supprimé. Ces entités étaient auparavant dans l’état géré, avant que vous ne les programmiez pour leur suppression. La suppression d’entités est en dehors de la portée de ce post, donc je n’entrerai pas dans trop de détails à ce sujet. Vous pouvez planifier la suppression d’une entité en appelant la méthode remove sur l’interface EntityManager.
Persistance d’une Nouvelle Entité À l’aide de persist Ou save
Lorsque vous créez un nouvel objet entity, il est dans l’état de cycle de vie transitoire. Il ne mappe aucun enregistrement de base de données.
Author a = new Author();a.setFirstName("Thorben");a.setLastName("Janssen");
Vous devez attacher l’entité à un contexte de persistance afin qu’elle soit gérée et conservée dans la base de données. Vous pouvez utiliser la méthode persist de JPA ou la méthode save d’Hibernate pour ce faire. Les deux méthodes semblent faire la même chose, mais il y a quelques différences.
Spécification vs. API propriétaire
La différence la plus évidente est que la spécification JPA définit la méthode persist. Vous pouvez l’utiliser avec toutes les implémentations JPA. La méthode de sauvegarde, en revanche, est spécifique à l’Hibernation. Il n’est donc pas disponible dans d’autres implémentations JPA.
Mais cela n’est pertinent que si vous souhaitez pouvoir remplacer Hibernate par une autre implémentation JPA, comme Eclipse Link ou OpenJPA.
Types De Retour Et Exécution D’Instructions SQL
Une autre différence évidente entre ces 2 méthodes est leur type de retour. La méthode persist de JPA renvoie void et la méthode save de Hibernate renvoie la clé primaire de l’entité.
Cela peut sembler une énorme différence, surtout lorsque vous regardez de plus près Javadoc d’Hibernate et la spécification JPA:
- Le Javadoc de la méthode save d’Hibernate indique qu’il génère d’abord la valeur de la clé primaire:
Persistez l’instance transitoire donnée, en attribuant d’abord un identifiant généré.
Session Javadoc.enregistrer (entité) - Vous ne trouvez aucune information à ce sujet dans la spécification JPA. Il ne définit pas quand la valeur de la clé primaire doit être affectée. Ainsi, le fournisseur de persistance peut le faire à tout moment entre l’appel de la méthode persist et le vidage du contexte de persistance.
Dans la plupart des cas, cela ne fait aucune différence si vous appelez la méthode save ou persist. Hibernate utilise le nom de la classe d’entité et la valeur de la clé primaire pour stocker l’entité dans le cache de premier niveau. Il a donc besoin d’une valeur de clé primaire lorsqu’il exécute la méthode persist.
Dans presque toutes les situations, Hibernate génère immédiatement la valeur de la clé primaire et déclenche une instruction SQL si nécessaire, lorsque vous appelez la méthode persist ou save.
Mais ce n’est pas le cas si vous utilisez la stratégie d’IDENTITÉ et essayez de conserver une entité sans transaction active ou avec FlushMode.MANUEL. Si vous appelez la méthode persist dans l’une de ces situations, Hibernate retarde l’exécution de l’instruction SQL INSERT et crée une valeur de clé primaire temporaire. Mais si vous appelez la méthode save, Hibernate exécute immédiatement l’instruction SQL INSERT et récupère la valeur de la clé primaire de la base de données.
Vous pouvez ensuite le récupérer en tant que valeur de retour de la méthode save.
Author a = new Author();a.setFirstName("Thorben");a.setLastName("Janssen");Long id = (Long) em.unwrap(Session.class).save(a);
Ou vous pouvez appeler la méthode getter de l’attribut de clé primaire de votre entité gérée si vous utilisez la méthode persist de JPA.
Author a = new Author();a.setFirstName("Torben");a.setLastName("Janssen");em.persist(a);Long id = a.getId();
Hibernate exécute les mêmes instructions SQL lorsque vous appelez la méthode persist ou save. Lequel et quand cela dépend de votre stratégie de génération de clé primaire:
Non généré
Si vous définissez la valeur de la clé primaire par programme, par exemple sur un identifiant naturel, Hibernate n’exécute une instruction SQL INSERT que lorsqu’elle vide le contexte de persistance.
14:08:34,979 INFO TestPersistSaveMerge:237 - Save entity14:08:35,052 INFO TestPersistSaveMerge:240 - Commit transaction14:08:35,123 DEBUG SQL:92 - insert into Author (firstName, lastName, version, id) values (?, ?, ?, ?)
Généré avec la stratégie d’identité
Si vous utilisez la stratégie d’IDENTITÉ pour générer la valeur de la clé primaire, Hibernate doit exécuter l’instruction INSERT lorsque vous appelez la méthode save ou persist pour récupérer la valeur de la clé primaire de la base de données.
14:09:28,264 INFO TestPersistSaveMerge:237 - Save entity14:09:28,336 DEBUG SQL:92 - insert into Author (firstName, lastName, version) values (?, ?, ?)14:09:28,354 INFO TestPersistSaveMerge:240 - Commit transaction
Généré avec la stratégie DE SÉQUENCE
Et si vous utilisez la SÉQUENCE, Hibernate exécute une instruction SQL SELECT pour récupérer la valeur suivante de la séquence de base de données. Hibernate retarde ensuite l’instruction INSERT jusqu’à ce qu’elle vide le contexte de persistance. Dans cet exemple, le vidage se produit lorsque la transaction est validée.
14:10:27,994 INFO TestPersistSaveMerge:237 - Save entity14:10:28,002 DEBUG SQL:92 - select nextval ('hibernate_sequence')14:10:28,042 INFO TestPersistSaveMerge:240 - Commit transaction14:10:28,096 DEBUG SQL:92 - insert into Author (firstName, lastName, version, id) values (?, ?, ?, ?)
Généré avec la stratégie de TABLE
Vous ne devez pas utiliser la stratégie de TABLE car elle nécessite des verrous au niveau des lignes sur la table de clés primaires et ne s’adapte pas bien. Si vous utilisez cette stratégie de toute façon, Hibernate exécute une instruction SQL SELECT pour récupérer la valeur de clé primaire suivante dans la base de données et écrit la nouvelle valeur dans la table de base de données. Il retarde l’exécution de l’instruction SQL INSERT pour la nouvelle entité jusqu’à ce qu’elle vide le contexte de persistance.
14:11:17,368 INFO TestPersistSaveMerge:237 - Save entity14:11:17,482 DEBUG SQL:92 - select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update of tbl14:11:17,531 DEBUG SQL:92 - insert into hibernate_sequences (sequence_name, next_val) values (?,?)14:11:17,534 DEBUG SQL:92 - update hibernate_sequences set next_val=? where next_val=? and sequence_name=?14:11:17,584 INFO TestPersistSaveMerge:240 - Commit transaction14:11:17,655 DEBUG SQL:92 - insert into Author (firstName, lastName, version, id) values (?, ?, ?, ?)
Lequel choisir ?
Vous pouvez vous attendre à ce que la méthode save et persist se comporte différemment car il existe quelques différences entre la spécification JPA et le Javadoc des méthodes propriétaires d’Hibernate.
Mais presque toutes ces différences disparaissent lorsque vous regardez l’implémentation interne. Les seuls qui restent sont les cas de coin 2 dans lesquels Hibernate pourrait retarder la récupération de la clé primaire, le type de retour de la méthode et la prise en charge par d’autres implémentations JPA.
Pour la plupart des applications, cela ne fait aucune différence si vous obtenez la valeur de clé primaire générée en tant que type de retour de la méthode de sauvegarde d’Hibernate ou à partir de la méthode getter de votre attribut de clé primaire. Tant que vous n’utilisez pas de contexte de persistance étendu et n’effectuez pas toutes les opérations de base de données avec une transaction active, je recommande d’utiliser la méthode persist de JPA.
Mise à jour d’une entité détachée
Lorsque vous fermez le contexte de persistance actuel ou supprimez explicitement une entité de celui-ci en appelant les méthodes clear ou detach sur l’interface EntityManager, l’entité devient détachée. Cela signifie qu’il n’est plus stocké dans le cache de 1er niveau et que Hibernate ne répliquera aucune des modifications appliquées à la base de données.
Vous pouvez utiliser la mise à jour d’Hibernate ou la méthode de fusion de JPA pour associer une entité détachée à un contexte de persistance. Une fois cela fait, Hibernate mettra à jour la base de données en fonction des valeurs d’attribut d’entité.
L’effet de la méthode de mise à jour et de fusion semble être le même, mais comme vous le verrez dans les sections suivantes, il y a une différence importante.
Méthode de fusion de JPA
La méthode de fusion de JPA copie l’état d’une entité détachée dans une instance gérée de la même entité. Hibernate exécute donc une instruction SQL SELECT pour récupérer une entité gérée à partir de la base de données. Si le contexte de persistance contenait déjà une instance gérée de l’entité, Hibernate utilise plutôt celle existante. Il copie ensuite toutes les valeurs d’attribut à l’entité gérée et les renvoie à l’appelant.
Author managedAuthor = em.merge(a);
Après avoir activé la journalisation des instructions SQL, vous pouvez voir les instructions SELECT et UPDATE exécutées dans la sortie du journal.
11:37:21,172 DEBUG SQL:92 - select books0_.bookId as bookId1_2_0_, books0_.authorId as authorId2_2_0_, book1_.id as id1_1_1_, book1_.fk_author as fk_autho6_1_1_, book1_.format as format2_1_1_, book1_.publishingDate as publishi3_1_1_, book1_.title as title4_1_1_, book1_.version as version5_1_1_, author2_.id as id1_0_2_, author2_.firstName as firstNam2_0_2_, author2_.lastName as lastName3_0_2_, author2_.version as version4_0_2_ from BookAuthor books0_ inner join Book book1_ on books0_.authorId=book1_.id left outer join Author author2_ on book1_.fk_author=author2_.id where books0_.bookId=?11:37:21,180 INFO TestPersistSaveMerge:82 - Before commit11:37:21,182 DEBUG SQL:92 - update Author set firstName=?, lastName=?, version=? where id=? and version=?
Lorsque Hibernate vide le contexte de persistance pour la prochaine fois, son mécanisme de vérification sale vérifie toutes les entités gérées. S’il détecte que l’opération de fusion a modifié une valeur d’attribut d’entité, il déclenche l’instruction SQL UPDATE requise.
Il y a un détail important que vous devez savoir lorsque vous utilisez la méthode de fusion de JPA. Hibernate copie les valeurs d’attribut de l’entité détachée dans l’entité gérée. Cela écrase toutes les modifications que vous avez effectuées sur cette entité au cours de la session en cours.
Méthode de mise à jour d’Hibernate
La méthode de mise à jour d’Hibernate ne déclenche pas d’instruction SQL SELECT. Il attache simplement l’entité au contexte de persistance actuel. Contrairement à la méthode de fusion de JPA, vous ne pouvez pas perdre de modifications en appelant la méthode de mise à jour. Si le contexte de persistance contient déjà une instance gérée de l’entité que vous souhaitez mettre à jour, une exception est levée.
em.unwrap(Session.class).update(a);
Lorsque Hibernate effectue la prochaine chasse d’eau, elle n’effectue aucune vérification sale. Ce n’est pas possible car Hibernate n’a pas lu la dernière version de l’entité dans la base de données. Il exécute simplement une instruction SQL UPDATE pour l’entité rattachée.
11:38:28,151 INFO TestPersistSaveMerge:121 - Before commit11:38:28,153 DEBUG SQL:92 - update Author set firstName=?, lastName=?, version=? where id=? and version=?
La vérification sale manquante provoque une instruction SQL UPDATE inutile lorsque l’entité et l’enregistrement de base de données correspondant contiennent les mêmes valeurs. Cela peut poser problème si votre DBA a enregistré un déclencheur de mise à jour pour la table de base de données. Dans ces situations, vous pouvez annoter votre entité avec @SelectBeforeUpdate.
@Entity@SelectBeforeUpdatepublic class Author { ... }
Qui indique à Hibernate de sélectionner l’entité et d’effectuer une vérification sale avant de générer l’instruction SQL UPDATE. Comme vous pouvez le voir dans la sortie du journal, le comportement de la méthode de mise à jour est maintenant similaire à la méthode de fusion de JPA.
19:08:16,530 INFO TestPersistSaveMerge:121 - Before commit19:08:16,531 DEBUG SQL:92 - select author_.id, author_.firstName as firstNam2_0_, author_.lastName as lastName3_0_, author_.version as version4_0_ from Author author_ where author_.id=?19:08:16,592 DEBUG SQL:92 - update Author set firstName=?, lastName=?, version=? where id=? and version=?
Mais il existe une différence significative entre les 2 méthodes. Lorsque vous appelez la méthode update, Hibernate sélectionne uniquement l’entité que vous avez fournie en tant que paramètre de méthode. Mais lorsque vous appelez la méthode de fusion de JPA, Hibernate sélectionnera également toutes les associations avec CascadeType.FUSIONNER. Vous devriez donc préférer la méthode de fusion de JPA si vous rattachez à nouveau un énorme graphique d’entités.
Lequel choisir ?
Il n’y a pas de réponse générale à ces questions. Comme vous l’avez vu, les deux méthodes ont leurs avantages et leurs inconvénients. Vous devez décider pour votre cas d’utilisation spécifique si Hibernate doit sélectionner l’entité avant de déclencher l’instruction SQL UPDATE. Et si c’est le cas, vous devez également prendre en compte la profondeur de votre graphique d’entité et les implications en termes de performances du comportement de récupération fourni.
Mise à jour d’une entité gérée
JPA et Hibernate facilitent la mise à jour d’une entité gérée. Si votre entité est dans l’état de cycle de vie géré, par exemple parce que vous l’avez récupérée avec une requête JPQL ou la méthode find de EntityManager, il vous suffit de modifier les valeurs de vos attributs d’entité.
em = emf.createEntityManager();em.getTransaction().begin();a = em.find(Author.class, a.getId());a.setFirstName("Thorben");log.info("Before commit");em.getTransaction().commit();em.close();
Lorsque Hibernate décide de vider le contexte de persistance, le mécanisme de vérification sale détecte la modification et exécute l’instruction SQL UPDATE requise.
11:41:49,178 DEBUG SQL:92 - select author0_.id as id1_0_0_, author0_.firstName as firstNam2_0_0_, author0_.lastName as lastName3_0_0_, author0_.version as version4_0_0_ from Author author0_ where author0_.id=?11:41:49,191 INFO TestPersistSaveMerge:335 - Before commit11:41:49,193 DEBUG SQL:92 - update Author set firstName=?, lastName=?, version=? where id=? and version=?
Vous n’en avez pas besoin et vous ne devez pas appeler la méthode save d’Hibernate après avoir mis à jour une entité. Cela déclenche un événement SaveOrUpdate supplémentaire sans fournir d’avantages. Lorsque Hibernate décide de vider le contexte de persistance, il effectuera de toute façon la vérification sale pour détecter toutes les modifications avant d’exécuter les instructions SQL UPDATE requises.