JPA EntityManager: Pourquoi utiliser persist () plutôt que merge ()?
EntityManager.merge()
peut insérer de nouveaux objets et mettre à jour des objets existants.
pourquoi vouloir utiliser persist()
(qui ne peut créer que de nouveaux objets)?
15 réponses
d'une manière ou d'une autre ajoutera une entity à un Contextec Persistenc, la différence est dans ce que vous faites avec l'entity par la suite.
Persist prend une instance d'entité, l'ajoute au contexte et la fait gérer (c'est-à-dire que les futures mises à jour de l'entité seront suivies).
Merge crée une nouvelle instance de votre entité, copie l'état de l'entité fournie, et fait la nouvelle copie gérée. L'exemple que vous transmettez ne seront pas gérés (tout les changements que vous apporterez ne feront pas partie de la transaction - à moins que vous appeliez à nouveau merge).
peut-être qu'un exemple de code 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 y a des situations où vous voudriez utiliser le scénario 2.
persistent et fusionnent sont pour deux buts différents (ils ne sont pas des alternatives du tout).
(édité à élargir les différences de l'information)
persistent:
- insérer un nouveau registre à la base de données
- joindre l'objet au gestionnaire de l'entité.
fusionner:
- trouvez un objet joint avec le même id et mettez-le à jour.
- si existe, mettez à jour et retournez l'objet déjà joint.
- s'il n'existe pas, insérer le nouveau Registre dans la base de données.
persistent() efficacité:
- il pourrait être plus efficace d'insérer un nouveau Registre dans une base de données que de fusionner().
- il ne duplique pas l'objet original.
persistent () sémantique:
- il s'assure que vous insérez et ne mettez pas à jour par erreur.
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 existe seulement 1 objet joint pour n'importe quel Registre dans le directeur d'entité.
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 si connecté à MySQL merge() puisse être aussi efficace que persist () en utilisant un appel à insérer avec L'option DUPLICATE KEY UPDATE, JPA est une programmation de très haut niveau et vous ne pouvez pas supposer que ce sera le cas partout.
si vous utilisez la génératrice assignée, en utilisant merge au lieu de persist peut causer une déclaration SQL redondante , affectant ainsi la performance.
aussi, appel de fusion pour les entités gérées est également une erreur puisque 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 sale sur flushing le Contexte de Persistance .
pour comprendre comment tout cela fonctionne, vous devez d'abord savoir que Hibernate déplace l'état d'esprit du développeur des déclarations SQL à transitions d'état d'entité .
une fois Qu'une entité est activement gérée par Hibernate, tous les changements seront automatiquement propagés à la base de données.
surveilles D'hibernation entités actuellement rattachées. Mais pour une entité géré, il doit être dans le bon état de l'entité.
tout D'abord, nous devons définir tous les états de l'entité:
-
Nouveau (Transitoire)
objet nouvellement créé qui n'a jamais été associé à un hibernation
Session
(A. K. aPersistence Context
) et n'est pas mappé à une ligne de table de base de données est considéré comme étant dans le nouvel état (transitoire).devenir persisté nous devons explicitement appel la méthode
EntityManager#persist
ou utiliser le mécanisme de persistance transitoire. -
Persistant (Géré)
une entité persistante a été associée à une rangée de tables de base de données et elle est gérée par le contexte courant de persistance. Tout changement apporté à cette entité sera détecté et propagé dans la base de données (pendant la session flush-time). Avec Hibernate, nous n'avons plus à exécuter INSERT / UPDATE/DELETE déclaration. Hibernate emploie un transactional write-behind style de travail et les changements sont synchronisés au tout dernier moment responsable, au cours de l'actuel
Session
flush-time. -
Détaché
une fois le contexte courant de persistance fermé, toutes les entités gérées précédemment se détachent. Les changements successifs ne seront plus suivis et aucune synchronisation automatique de la base de données n'est va arriver.
pour associer une entité détachée à une session D'hibernation active, vous pouvez choisir l'une des options suivantes:
-
Rattachement
Hibernate (mais pas JPA 2.1) supporte le rattachement par la méthode Session#update. Une session Hibernate ne peut associer qu'un objet Entity pour une ligne de base de données Donnée. C'est parce que le Contexte de Persistance agit comme un cache en mémoire (premier niveau cache) et une seule valeur (entity) est associée à une clé donnée (type d'entité et Identificateur de base de données). Une entité ne peut être rattachée à nouveau que s'il n'y a pas d'autre objet JVM (correspondant à la même ligne de base de données) déjà associé à la Session D'hibernation en cours.
-
Fusion
la fusion va copier l'état de l'entité détachée (source) vers une instance de l'entité gérée (destination). Si l'entité qui fusionne n'a pas d'équivalent dans la Session en cours, un sera récupéré à partir de la base de données. Le détachement de l'objet instance continuera à demeurer même après l'opération de fusion.
-
-
enlevé
bien que JPA exige que seules les entités gérées puissent être supprimées, Hibernate peut également supprimer les entités détachées (mais seulement par un appel de méthode Session#delete). Une entité retirée est seulement prévue pour la suppression et le réel L'instruction de suppression de base de données sera exécutée pendant la session flush-time.
pour mieux comprendre les transitions D'état JPA, vous pouvez visualiser le diagramme suivant:
ou si vous utilisez L'API spécifique D'hibernation:
j'ai remarqué que lorsque j'ai utilisé em.merge
, j'ai reçu une instruction SELECT
pour chaque INSERT
, même s'il n'y avait pas de champ que JPA générait pour moi--le champ de clé primaire était un UUID que je me suis fixé. Je suis passé à em.persist(myEntityObject)
et j'ai eu juste INSERT
alors.
la spécification JPA dit ce qui suit au sujet de persist()
.
si X est un objet détaché, le
EntityExistsException
l'opération est invoquée, ou leEntityExistsException
ou un autrePersistenceException
peut être jeté à la chasse d'eau ou à l'Heure de commettre.
ainsi utiliser persist()
serait approprié lorsque l'objet ne devrait pas être un détaché objet. Vous pourriez préférer que le code jette le PersistenceException
pour qu'il échoue rapidement.
bien que la spécification n'est pas claire , persist()
pourrait définir le @GeneratedValue
@Id
pour un objet. merge()
doit cependant avoir un objet avec le @Id
déjà généré.
plus de détails sur merge qui vous aidera à utiliser merge over persist:
retourner une instance gérée autre que l'entité d'origine est un élément critique de la fusion processus. Si une instance d'entité avec le même identifiant existe déjà dans le contexte de persistance, l' fournisseur de remplacer son état avec l'état de l'entité fusionnée, mais la gestion de la version qui existait déjà doit être retournée au client afin qu'il peut être utilisé. Si le fournisseur n'a pas mettre à jour l'instance de L'employé dans le contexte de la persistance, toute référence à cette instance deviendra incompatible avec la fusion du nouvel état.
lorsque merge() est invoquée sur une nouvelle entité, elle se comporte de la même manière que l'opération persist (). Il ajoute l'entity dans le contexte de la persistance, mais au lieu d'ajouter l'instance entity originale, il crée une nouvelle copie et gère cette instance à la place. La copie qui est créé par la fusion() l'opération est persisté comme si la méthode persist() avait été invoquée dessus.
en présence de relations, l'opération merge() tentera de mettre à jour l'entité gérée pour pointer vers les versions gérées des entités référencées par l'entité détachée. Si l'entité a un relation à un objet qui n'a pas d'identité persistante, le résultat de l'opération de fusion est indéterminé. Certains fournisseurs pourraient permettre à la copie gérée de pointer vers non-objet persistant, tandis que d'autres pourraient jeter une exception immédiatement. La fusion() opération peut éventuellement être en cascade dans ces cas pour éviter qu'une exception ne se produise. Nous couvrirons la cascade de la fusion() opération plus loin dans cette section. Si une entité fusionnée pointe vers une entité supprimée, L'exception d'illégalargumentexception sera lancée.
Lazy-loading les relations sont un cas particulier de l'opération de fusion. Si un chargement paresseux la relation n'a pas été déclenchée sur une entité avant qu'elle ne se détache, cette relation sera ignorée lorsque l'entité est fusionnée. Si la relation a été déclenchée pendant la gestion puis réglée à null pendant que l'entité a été détachée, la version gérée de l'entité aura également la relation effacée pendant la fusion."
tous les renseignements ci-dessus ont été tirés de "Pro JPA 2 Mastering the Java™ Persistence API" par Mike Keith et Merrick Schnicariol. Chapitre 6. Détachement de la Section et fusion. Ce livre est en fait un deuxième livre consacré à JPA par les auteurs. Ce nouveau livre a beaucoup de nouvelles informations 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 façon anonyme ma première réponse.
il y a d'autres différences entre merge
et persist
(je vais énumérer à nouveau ceux déjà affichés ici):
D1. merge
ne fait pas gérer l'entité passée, mais renvoie plutôt une autre instance qui est gérée. persist
de l'autre côté fera gérer l'entité passé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 ensuite décider de conserver l'entité, vous pouvez le faire uniquement avec persist(), parce que merge
va lancer un IllegalArgumentException
.
D3. Si vous avez décidé de prendre soin manuellement de vos pièces D'identité (p. ex. g en utilisant UUIDs), puis a merge
l'opération déclenchera des requêtes SELECT
ultérieures afin de rechercher des entités existantes avec cette ID, tandis que persist
peut ne pas avoir besoin de ces requêtes.
D4. Il ya des cas où vous n'avez tout simplement pas confiance dans le code qui appelle votre code, et afin de s'assurer qu'aucune donnée n'est mis à jour, mais est plutôt inséré, vous devez utiliser persist
.
j'obtenais des exceptions de chargement Lazy sur mon entité parce que j'essayais d'accéder à une collection chargée paresseuse qui était en session.
ce que je ferais était dans une requête séparée, récupérer l'entité de la session et ensuite essayer d'accéder à une collection dans ma page jsp qui était problématique.
pour pallier cela, j'ai mis à jour la même entité dans mon controller et l'ai passé à mon jsp, bien que j'imagine quand j'ai re-enregistré en session qu'il sera aussi accessible par SessionScope
et ne pas jeter un 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!!
en parcourant les réponses, il manque quelques détails concernant la "Cascade" et la génération d'identité. Voir question
aussi, il est intéressant de mentionner que vous pouvez avoir séparé Cascade
annotations pour la fusion et la persistance: Cascade.MERGE
et Cascade.PERSIST
qui seront traités selon la méthode utilisée.
La spec est votre ami ;)
Scénario X:
Table:Spitter (Un) ,la Table: Crachats (Beaucoup) (Crachats est Propriétaire de la relation avec un FK:spitter_id)
ce scénario se traduit par une économie : le Spitter et les deux Spittles comme si elles 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 crachoirs mais ils ne feront pas référence 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.save(spittle3); // <--merge!!
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
j'ai trouvé cette explication des Docs Hibernate instructive, parce qu'ils contiennent un cas d'utilisation:
l'usage et la sémantique de merge() semblent dérouter les nouveaux utilisateurs. Tout d'abord, tant que vous n'essayez pas d'utiliser l'objet d'etat chargé dans un gestionnaire d'entité dans une autre nouvelle entité gestionnaire, vous devez pas besoin d'utiliser merge() à tous les . Certaines applications entières n'utiliseront jamais cette méthode.
Généralement merge() est utilisé dans le scénario suivant:
- l'application charge un objet dans le premier gestionnaire d'entité
- l'objet est passé à la couche de présentation
- certaines modifications sont apportées à l'objet
- l'objet est redonné à la couche business logic
- l'application persiste ces modifications en appelant merge() dans un deuxième entité gestionnaire
Voici la sémantique exacte de merge ():
- s'il existe une instance gérée avec le même identifiant associé au contexte de persistance, copier l'état de l'objet donné sur l'instance gérée
- s'il n'y a pas d'instance gérée actuellement associée au contexte de persistance, essayez de la charger à partir de la base de données, ou créez une nouvelle instance gérée
- l'instance gérée est retournée
- l'instance donnée ne devient pas associée au contexte de persistance, elle reste détachée et est habituellement écartée
de: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
JPA est indiscutablement une grande simplification dans le domaine de l'entreprise applications construites sur la plate-forme Java. En tant que développeur qui devait faire face aux complexités de l'ancienne entité haricots en J2EE je vois le l'inclusion de JPA dans les spécifications Java EE est un grand pas en avant transmettre. Cependant, tout en approfondissant les détails de L'app, je trouve que les choses ne sont pas si faciles. Dans cet article je traite de la comparaison de les méthodes merge et persist D'EntityManager dont les qui se chevauchent le comportement peut causer de la confusion, non seulement pour un débutant. En Outre, J' proposer une généralisation qui considère les deux méthodes comme des cas particuliers de méthode plus générale combiner.
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 de persist peut être résumé comme suit:
" une instance nouvellement créée de la classe entity est passée à la méthode persist. Après cette méthode retourne, l'entité est gérée et prévu pour l'insertion dans la base de données. Cela peut se produire au moment ou avant la transaction ou lorsque la méthode flush est appelée. Si l'entité fait référence à une autre entité par le biais d'une relation marquée avec la stratégie en cascade PERSIST, cette procédure s'applique également à celle-ci."
la spécification va plus dans les détails, cependant, se souvenir d'eux n'est pas crucial car ces détails couvrent plus ou moins des situations exotiques seulement.
entités fusionnantes
par rapport à persist, la description du comportement de la fusion n'est pas si simple. Il n'y a pas de scénario principal, comme dans le cas de persist, et un programmeur doit se souvenir de tous les scénarios afin d'écrire un code correct. Il semble à mon avis, les concepteurs de L'app voulaient avoir une méthode dont la principale préoccupation serait de traiter les entités détachées (à l'opposé de la méthode de persist qui traite principalement des entités nouvellement créées).) La principale tâche de la méthode de fusion est de transférer l'état d'une entité non gérée (transmise comme argument) à son homologue gérée dans le contexte de la persistance. Cette tâche, cependant, divise davantage en plusieurs scénarios qui aggravent l'intelligibilité du comportement de la méthode globale.
au lieu de répéter les paragraphes de la spécification JPA, j'ai préparé un diagramme qui décrit schématiquement le comportement de la méthode de fusion:
alors, Quand dois-je utiliser persister et quand fusionner?
persist
- Vous voulez la méthode toujours crée une nouvelle entité et ne met jamais à jour une entité. Sinon, la méthode jette une exception en conséquence de la violation de l'unicité de la clé primaire.
- procédés par lots, traitement des entités de manière stateful (voir modèle de passerelle).
- optimisation des performances
fusion
- vous voulez que la méthode insère ou mette à jour une entité dans la base de données.
- Vous voulez gérer des entités dans un apatride (objets de transfert de données dans les services)
- vous voulez insérer une nouvelle entité qui peut avoir une référence à une autre entité qui peut mais ne peut pas encore être créée (la relation doit être marquée Fusion). Par exemple, en insérant une nouvelle photo avec une référence à un nouvel album ou à un album préexistant.
vous êtes peut-être venu ici pour savoir quand utiliser persist et quand utiliser merge . Je pense que cela dépend de la situation: est-il probable que vous avez besoin pour créer un nouvel enregistrement et la façon dont il est difficile de récupérer des données persistantes.
supposons que vous pouvez utiliser une clé/identificateur naturel.
-
les données doivent être maintenues, mais de temps à autre, un document existe et une mise à jour est appelé pour. Dans ce cas, vous pouvez essayer une persist et si elle jette une EntityExistsException, vous cherchez et combinez les données:
try {entityManager.persist(entité) }
catch(EntityExistsException exception) { /* récupérer et fusionner */ }
-
les données sur la persistance doivent être mises à jour, mais de temps à autre, il n'y a pas encore d'enregistrement des données. Dans ce cas, vous regardez vers le haut, et faire un persister si l'entité est manquant:
entité = entityManager.trouver(clé);
si (entité == null) { entityManager.persist(entité); }
else { /* fusion */ }
si vous n'avez pas de clé/identificateur naturel, vous aurez plus de difficulté à déterminer si l'entité existe ou non, ou comment la chercher.
Les fusions peuvent être traités de deux manières, aussi:
- si les changements sont habituellement mineurs, les appliquer à l'entité gérée.
- si les changements sont fréquents, copiez L'ID de l'entité persistée, ainsi que les données non modifiées. Ensuite, appelez EntityManager:: merge() pour remplacer l'ancien contenu.
persist (entity) doit être utilisé avec des entities totalement nouvelles, pour les ajouter à DB (si entity existe déjà dans DB il y aura Entityexpistsexception throw).
fusionner(entité) doit être utilisé pour mettre entité contexte de persistance si l'entité s'est détaché et a été changé.
probablement persist génère la déclaration INSERT sql et la déclaration merge UPDATE sql (mais je ne suis pas sûr).
autre observation:
merge()
ne se souciera d'un id généré automatiquement(testé sur IDENTITY
et SEQUENCE
) que lorsqu'un enregistrement avec un tel id existe déjà dans votre table. Dans ce cas, merge()
essaiera de mettre à jour le document.
Si, toutefois, un id est absent ou ne correspondant à aucune des enregistrements existants, merge()
d'ignorer complètement et demander un db d'allouer un nouveau. C'est parfois une source de beaucoup de bugs. N'utilisez pas merge()
pour forcez une identification pour un nouveau record.
persist()
d'un autre côté ne vous laissera jamais lui passer une pièce d'identité. Elle échouera immédiatement. Dans mon cas, c'est:
causé par: org.hiberner.PersistentObjectException: entité détachée passé à persister
hibernation-jpa javadoc a un indice:
lancers : javax.persistance.EntityExistsException - si l'entité il existe déjà. (Si l'entité qui existe déjà, l' EntityExistsException peut être lancé lorsque l'opération persist est invoqué, ou L'EntityExistsException ou une autre exception de persistance peut être jeté à la chasse d'eau ou commettre le temps.)