EntityManager.merge()
peut insérer de nouveaux objets et mettre à jour ceux existants.
Pourquoi voudrait-on utiliser persist()
(qui ne peut que créer de nouveaux objets)?
EntityManager.merge()
peut insérer de nouveaux objets et mettre à jour ceux existants.
Pourquoi voudrait-on utiliser persist()
(qui ne peut que créer de nouveaux objets)?
Réponses:
Dans les deux cas, une entité sera ajoutée à un PersistenceContext, la différence réside dans ce que vous faites ensuite avec l'entité.
Persist prend une instance d'entité, l'ajoute au contexte et gère cette instance (c'est-à-dire que les futures mises à jour de l'entité seront suivies).
La fusion renvoie l'instance gérée vers laquelle l'état a été fusionné. Il retourne quelque chose qui existe dans PersistenceContext ou crée une nouvelle instance de votre entité. Dans tous les cas, il copiera l'état de l'entité fournie et retournera la copie gérée. L'instance que vous transmettez ne sera pas gérée (toutes les modifications que vous apporterez ne feront pas partie de la transaction - sauf si vous appelez à nouveau la fusion). Vous pouvez utiliser une instance retournée (une gérée).
Peut-être qu'un exemple de code vous aidera.
MyEntity e = new MyEntity();
// scenario 1
// tran starts
em.persist(e);
e.setSomeField(someValue);
// tran ends, and the row for someField is updated in the database
// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue);
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)
// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue);
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)
Les scénarios 1 et 3 sont à peu près équivalents, mais il existe certaines situations où vous souhaitez utiliser le scénario 2.
merge
la copie complète d'un objet avant de le gérer a un impact sur les performances?
@GeneratedId
puis-je l'obtenir dans le scénario 2?
La persistance et la fusion ont deux objectifs différents (ce ne sont pas du tout des alternatives).
(modifié pour développer les informations sur les différences)
persister:
fusionner:
efficacité persist ():
sémantique persist ():
Exemple:
{
AnyEntity newEntity;
AnyEntity nonAttachedEntity;
AnyEntity attachedEntity;
// Create a new entity and persist it
newEntity = new AnyEntity();
em.persist(newEntity);
// Save 1 to the database at next flush
newEntity.setValue(1);
// Create a new entity with the same Id than the persisted one.
AnyEntity nonAttachedEntity = new AnyEntity();
nonAttachedEntity.setId(newEntity.getId());
// Save 2 to the database at next flush instead of 1!!!
nonAttachedEntity.setValue(2);
attachedEntity = em.merge(nonAttachedEntity);
// This condition returns true
// merge has found the already attached object (newEntity) and returns it.
if(attachedEntity==newEntity) {
System.out.print("They are the same object!");
}
// Set 3 to value
attachedEntity.setValue(3);
// Really, now both are the same object. Prints 3
System.out.println(newEntity.getValue());
// Modify the un attached object has no effect to the entity manager
// nor to the other objects
nonAttachedEntity.setValue(42);
}
De cette façon, il n'existe qu'un seul objet attaché pour n'importe quel registre dans le gestionnaire d'entités.
merge () pour une entité avec un id est quelque chose comme:
AnyEntity myMerge(AnyEntity entityToSave) {
AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
if(attached==null) {
attached = new AnyEntity();
em.persist(attached);
}
BeanUtils.copyProperties(attached, entityToSave);
return attached;
}
Bien que s'il est connecté à MySQL merge () puisse être aussi efficace que persist () en utilisant un appel à INSERT avec l'option ON DUPLICATE KEY UPDATE, JPA est une programmation de très haut niveau et vous ne pouvez pas supposer que cela va être le cas partout.
em.persist(x)
avec x = em.merge(x)
?
merge()
peut également lancer unEntityExistsException
RuntimeException
, mais ce n'est pas mentionné dans le Javadoc.
Si vous utilisez le générateur attribué, utilisation de la fusion au lieu de la persistance peut entraîner une instruction SQL redondante , affectant ainsi les performances.
De plus, appeler la fusion pour les entités gérées est également une erreur car les entités gérées sont automatiquement gérées par Hibernate et leur état est synchronisé avec l'enregistrement de la base de données par le mécanisme de vérification incorrect lors de vidage du contexte de persistance .
Pour comprendre comment tout cela fonctionne, vous devez d'abord savoir que Hibernate fait évoluer l'état d'esprit des développeurs des instructions SQL vers transitions d'état d'entité .
Une fois qu'une entité est activement gérée par Hibernate, toutes les modifications vont être automatiquement propagées à la base de données.
Hibernate surveille les entités actuellement attachées. Mais pour qu'une entité soit gérée, elle doit être dans le bon état d'entité.
Pour mieux comprendre les transitions d'état JPA, vous pouvez visualiser le diagramme suivant:
Ou si vous utilisez l'API spécifique Hibernate:
Comme illustré par les diagrammes ci-dessus, une entité peut se trouver dans l'un des quatre états suivants:
Nouveau (transitoire)
Un objet nouvellement créé qui n'a jamais été associé à un Hibernate Session
(aka Persistence Context
) et qui n'est mappé à aucune ligne de table de base de données est considéré comme étant dans l'état New (Transient).
Pour devenir persistant, nous devons soit appeler explicitement la EntityManager#persist
méthode, soit utiliser le mécanisme de persistance transitive.
Persistant (géré)
Une entité persistante a été associée à une ligne de table de base de données et elle est gérée par le contexte de persistance en cours d'exécution. Toute modification apportée à une telle entité va être détectée et propagée à la base de données (pendant le vidage de session). Avec Hibernate, nous n'avons plus à exécuter les instructions INSERT / UPDATE / DELETE. Hibernate utilise un style de travail transactionnel à écriture différée et les modifications sont synchronisées au tout dernier moment responsable, pendant le Session
temps de vidage actuel .
Détaché
Une fois le contexte de persistance en cours d'exécution fermé, toutes les entités précédemment gérées se détachent. Les modifications successives ne seront plus suivies et aucune synchronisation automatique de la base de données ne se produira.
Pour associer une entité détachée à une session de mise en veille prolongée active, vous pouvez choisir l'une des options suivantes:
Rattachement
Hibernate (mais pas JPA 2.1) prend en charge le rattachement via la méthode de mise à jour Session #. Une session Hibernate ne peut associer qu'un seul objet Entity pour une ligne de base de données donnée. En effet, le contexte de persistance agit comme un cache en mémoire (cache de premier niveau) et une seule valeur (entité) est associée à une clé donnée (type d'entité et identifiant de base de données). Une entité ne peut être rattachée que si aucun autre objet JVM (correspondant à la même ligne de base de données) n'est déjà associé à la session Hibernate en cours.
Fusion
La fusion va copier l'état de l'entité détachée (source) vers une instance d'entité gérée (destination). Si l'entité fusionnée n'a pas d'équivalent dans la session en cours, une sera extraite de la base de données. L'instance d'objet détaché restera détachée même après l'opération de fusion.
Supprimé
Bien que JPA exige que seules les entités gérées soient supprimées, Hibernate peut également supprimer les entités détachées (mais uniquement via un appel de méthode de suppression de session #). La suppression d'une entité supprimée est uniquement planifiée et l'instruction DELETE de la base de données réelle sera exécutée pendant le vidage de session.
J'ai remarqué que lorsque j'utilisais em.merge
, j'obtenais une SELECT
déclaration pour chaque INSERT
, même lorsqu'il n'y avait aucun champ que JPA générait pour moi - le champ de clé primaire était un UUID que je me suis défini. Je suis alors passé à em.persist(myEntityObject)
et n'ai eu que des INSERT
déclarations.
merge()
. J'avais une base de données PostgreSQL avec une vue compliquée : la vue agrégeait les données de plusieurs tables (les tables avaient une structure identique mais des noms différents). JPA a donc essayé de le faire merge()
, mais en fait JPA a d'abord été créé SELECT
(la base de données en raison des paramètres d'affichage peut renvoyer plusieurs enregistrements avec la même clé primaire de différentes tables!), Puis JPA (Hibernate était une implémentation) a échoué: il y a plusieurs enregistrements avec la même clé ( org.hibernate.HibernateException: More than one row with the given identifier was found
). Dans mon cas, cela persist()
m'a aidé.
La spécification JPA dit ce qui suit à propos de persist()
.
Si X est un objet détaché, le
EntityExistsException
peut être levé lorsque l'opération de persistance est invoquée, ou leEntityExistsException
ou un autrePersistenceException
peut être levé lors du vidage ou de la validation.
L'utilisation persist()
serait donc appropriée lorsque l'objet ne devrait pas être un objet détaché. Vous préférerez peut-être que le code lance le PersistenceException
afin qu'il échoue rapidement.
Bien que la spécification ne soit pas claire , persist()
peut définir le @GeneratedValue
@Id
pour un objet. merge()
cependant doit avoir un objet avec le @Id
déjà généré.
merge()
cependant doit avoir un objet avec le @Id
déjà généré . ". Chaque fois que l'EntityManager ne trouve pas de valeur pour le champ de l'ID d'objet, il est conservé (inséré) dans la base de données.
Quelques détails supplémentaires sur la fusion qui vous aideront à utiliser la fusion sur la persistance:
Le renvoi d'une instance gérée autre que l'entité d'origine est un élément essentiel du processus de fusion. Si une instance d'entité avec le même identifiant existe déjà dans le contexte de persistance, le fournisseur remplacera son état par l'état de l'entité qui est fusionnée, mais la version gérée qui existait déjà doit être retournée au client afin qu'elle puisse être utilisé. Si le fournisseur n'a pas mis à jour l'instance Employee dans le contexte de persistance, toute référence à cette instance deviendra incohérente avec le nouvel état fusionné.
Lorsque merge () est invoqué sur une nouvelle entité, il se comporte de la même manière que l'opération persist (). Il ajoute l'entité au contexte de persistance, mais au lieu d'ajouter l'instance d'entité d'origine, il crée une nouvelle copie et gère cette instance à la place. La copie créée par l'opération merge () est conservée comme si la méthode persist () y était invoquée.
En présence de relations, l'opération merge () tentera de mettre à jour l'entité gérée pour pointer vers des versions gérées des entités référencées par l'entité détachée. Si l'entité a une relation avec un objet qui n'a pas d'identité persistante, le résultat de l'opération de fusion n'est pas défini. Certains fournisseurs peuvent autoriser la copie gérée à pointer vers l'objet non persistant, tandis que d'autres peuvent lever immédiatement une exception. L'opération merge () peut éventuellement être mise en cascade dans ces cas pour éviter qu'une exception ne se produise. Nous couvrirons la cascade de l'opération merge () plus loin dans cette section. Si une entité en cours de fusion pointe vers une entité supprimée, une exception IllegalArgumentException sera levée.
Les relations de chargement différé sont un cas particulier dans l'opération de fusion. Si une relation de chargement différé n'a pas été déclenchée sur une entité avant de se détacher, cette relation sera ignorée lors de la fusion de l'entité. Si la relation a été déclenchée lors de sa gestion, puis définie sur null lors du détachement de l'entité, la version gérée de l'entité verra également la relation effacée lors de la fusion. "
Toutes les informations ci-dessus sont extraites de "Pro JPA 2 Mastering the Java ™ Persistence API" de Mike Keith et Merrick Schnicariol. Chapitre 6. Détachement et fusion de sections. Ce livre est en fait un deuxième livre consacré à JPA par les auteurs. Ce nouveau livre contient de nombreuses informations nouvelles puis anciennes. J'ai vraiment recommandé de lire ce livre pour ceux qui seront sérieusement impliqués avec JPA. Je suis désolé d'avoir posté de manière anonyme ma première réponse.
Il y a encore plus de différences entre merge
etpersist
(je vais énumérer à nouveau celles déjà publiées ici):
D1. merge
ne gère pas l'entité transmise, mais renvoie plutôt une autre instance gérée. persist
de l'autre côté, l'entité transmise sera gérée:
//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);
//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);
D2. Si vous supprimez une entité et décidez ensuite de la conserver, vous ne pouvez le faire qu'avec persist (), car merge
jettera un IllegalArgumentException
.
D3. Si vous avez décidé de prendre soin manuellement de vos identifiants (par exemple en utilisant des UUID), une merge
opération déclenchera des SELECT
requêtes ultérieures afin de rechercher les entités existantes avec cet identifiant, tout en persist
n'ayant pas besoin de ces requêtes.
D4. Il y a des cas où vous ne faites tout simplement pas confiance au code qui appelle votre code, et afin de vous assurer qu'aucune donnée n'est mise à jour, mais plutôt insérée, vous devez utiliser persist
.
J'obtenais des exceptions lazyLoading sur mon entité parce que j'essayais d'accéder à une collection chargée paresseux qui était en session.
Ce que je ferais, c'était dans une demande distincte, récupérer l'entité de la session, puis essayer d'accéder à une collection dans ma page jsp qui était problématique.
Pour remédier à cela, j'ai mis à jour la même entité dans mon contrôleur et l'ai passée à mon jsp, bien que j'imagine quand j'ai réenregistré en session qu'elle sera également accessible SessionScope
et ne lâchera pas LazyLoadingException
, une modification de l'exemple 2:
Ce qui suit a fonctionné pour moi:
// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"
//access e from jsp and it will work dandy!!
J'ai trouvé cette explication des documents Hibernate éclairante, car ils contiennent un cas d'utilisation:
L'utilisation et la sémantique de merge () semblent être déroutantes pour les nouveaux utilisateurs. Premièrement, tant que vous n'essayez pas d'utiliser l'état d'objet chargé dans un gestionnaire d'entités dans un autre nouveau gestionnaire d'entités, vous ne devriez pas du tout avoir besoin d'utiliser merge () . Certaines applications entières n'utiliseront jamais cette méthode.
Merge () est généralement utilisé dans le scénario suivant:
- L'application charge un objet dans le premier gestionnaire d'entités
- l'objet est transmis à la couche de présentation
- quelques modifications sont apportées à l'objet
- l'objet est retransmis à la couche logique métier
- l'application persiste ces modifications en appelant merge () dans un deuxième gestionnaire d'entités
Voici la sémantique exacte de merge ():
- s'il existe une instance gérée avec le même identifiant actuellement associé au contexte de persistance, copiez l'état de l'objet donné sur l'instance gérée
- si aucune instance gérée n'est actuellement associée au contexte de persistance, essayez de la charger depuis la base de données ou créez une nouvelle instance gérée
- l'instance gérée est renvoyée
- l'instance donnée ne devient pas associée au contexte de persistance, elle reste détachée et est généralement supprimée
De: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
En parcourant les réponses, il manque quelques détails concernant la cascade et la génération d'ID. Voir la question
En outre, il convient de mentionner que vous pouvez avoir des Cascade
annotations distinctes pour la fusion et la persistance: Cascade.MERGE
etCascade.PERSIST
qui seront traitées selon la méthode utilisée.
La spécification est votre amie;)
JPA est incontestablement une grande simplification dans le domaine des applications d'entreprise construites sur la plate-forme Java. En tant que développeur qui a dû faire face aux subtilités des anciens beans entité dans J2EE, je vois l'inclusion de JPA parmi les spécifications Java EE comme un grand pas en avant. Cependant, tout en approfondissant les détails de l'APP, je trouve des choses qui ne sont pas si faciles. Dans cet article, je traite de la comparaison des méthodes de fusion et de persistance d'EntityManager dont le comportement de chevauchement peut causer de la confusion non seulement à un débutant. De plus, je propose une généralisation qui voit les deux méthodes comme des cas particuliers d'une méthode plus générale se combinent.
Entités persistantes
Contrairement à la méthode de fusion, la méthode persist est assez simple et intuitive. Le scénario le plus courant de l'utilisation de la méthode persist peut être résumé comme suit:
"Une instance nouvellement créée de la classe d'entité est transmise à la méthode persist. Après le retour de cette méthode, l'entité est gérée et planifiée pour insertion dans la base de données. Cela peut se produire au moment de la validation de la transaction ou avant l'appel de la méthode flush. Si l'entité fait référence à une autre entité via une relation marquée avec la stratégie de cascade PERSIST, cette procédure lui est également appliquée. "
La spécification va plus dans les détails, cependant, leur souvenir n'est pas crucial car ces détails ne couvrent que des situations plus ou moins exotiques.
Fusion d'entités
En comparaison de persister, la description du comportement de la fusion n'est pas si simple. Il n'y a pas de scénario principal, comme c'est le cas pour persist, et un programmeur doit se souvenir de tous les scénarios afin d'écrire un code correct. Il me semble que les concepteurs JPA voulaient avoir une méthode dont la principale préoccupation serait de gérer les entités détachées (contrairement à la méthode persistante qui traite principalement des entités nouvellement créées.) La tâche principale de la méthode de fusion est de transférer l'état d'un entité non gérée (passée comme argument) à son homologue géré dans le contexte de persistance. Cette tâche, cependant, se divise en plusieurs scénarios qui aggravent l'intelligibilité du comportement global de la méthode.
Au lieu de répéter les paragraphes de la spécification JPA, j'ai préparé un organigramme qui décrit schématiquement le comportement de la méthode de fusion:
Alors, quand dois-je utiliser persist et quand fusionner?
persister
fusionner
Scénario X:
Table: Spitter (One), Table: Spittles (Many) (Spittles est propriétaire de la relation avec un FK: spitter_id)
Ce scénario entraîne la sauvegarde du Spitter et des deux Spittles comme s'ils appartenaient au même Spitter.
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.addSpittle(spittle3); // <--persist
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Scénario Y:
Cela sauvera le Spitter, sauvera les 2 Spittles Mais ils ne référenceront pas le même Spitter!
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.save(spittle3); // <--merge!!
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Une autre observation:
merge()
ne se souciera d'un identifiant généré automatiquement (testé sur IDENTITY
et SEQUENCE
) que lorsqu'un enregistrement avec un tel identifiant existe déjà dans votre table. Dans ce cas merge()
, essaiera de mettre à jour l'enregistrement. Si, cependant, un identifiant est absent ou ne correspond à aucun enregistrement existant, merge()
l'ignorera complètement et demandera à une base de données d'en allouer un nouveau. C'est parfois une source de nombreux bugs. Ne pas utiliser merge()
pour forcer un identifiant pour un nouvel enregistrement.
persist()
d'un autre côté, il ne vous permettra même pas de lui transmettre un identifiant. Cela échouera immédiatement. Dans mon cas, c'est:
Causée par: org.hibernate.PersistentObjectException: entité détachée passée pour persister
hibernate-jpa javadoc a un indice:
Lancers francs : javax.persistence.EntityExistsException - si l'entité existe déjà. (Si l'entité existe déjà, l'EntityExistsException peut être levée lorsque l'opération de persistance est invoquée, ou l'EntityExistsException ou une autre PersistenceException peut être levée au moment du vidage ou de la validation.)
persist()
ne se plaindra pas d'avoir un ID, il ne se plaint que lorsque quelque chose avec le même ID est déjà dans la base de données.
Vous êtes peut-être venu ici pour savoir quand utiliser la persistance et quand utiliser la fusion . Je pense que cela dépend de la situation: quelle est la probabilité que vous ayez besoin de créer un nouvel enregistrement et à quel point est-il difficile de récupérer des données persistantes.
Supposons que vous puissiez utiliser une clé / un identifiant naturel.
Les données doivent être conservées, mais de temps en temps un enregistrement existe et une mise à jour est nécessaire. Dans ce cas, vous pouvez essayer une persistance et si elle lève une exception EntityExistsException, vous la recherchez et combinez les données:
essayez {entityManager.persist (entity)}
catch (exception EntityExistsException) {/ * récupérer et fusionner * /}
Les données persistantes doivent être mises à jour, mais de temps en temps, il n'y a pas encore d'enregistrement pour les données. Dans ce cas, vous le recherchez et effectuez une persistance si l'entité est manquante:
entity = entityManager.find (clé);
if (entity == null) {entityManager.persist (entity); }
sinon {/ * fusionner * /}
Si vous n'avez pas de clé / identificateur naturel, vous aurez plus de mal à déterminer si l'entité existe ou non, ou comment la rechercher.
Les fusions peuvent également être traitées de deux manières:
persist (entity) doit être utilisé avec des entités totalement nouvelles, pour les ajouter à la base de données (si l'entité existe déjà dans la base de données, il y aura un lancer EntityExistsException).
merge (entity) doit être utilisé pour remettre l'entité au contexte de persistance si l'entité a été détachée et a été modifiée.
La persistance est probablement la génération de l'instruction SQL INSERT et la fusion de l'instruction SQL UPDATE (mais je ne suis pas sûr).