web-dev-qa-db-fra.com

JPA EntityManager: Pourquoi utiliser persist () sur merge ()?

EntityManager.merge() peut insérer de nouveaux objets et mettre à jour ceux existants.

Pourquoi voudrait-on utiliser persist() (qui ne peut créer que de nouveaux objets)?

910
Aaron Digulla

De toute façon, une entité sera ajoutée à un contexte de persistance, la différence réside dans ce que vous ferez avec l'entité par la suite.

Persist prend une instance d'entité, l'ajoute au contexte et rend cette instance gérée (c'est-à-dire que les mises à jour futures de l'entité feront l'objet d'un suivi).

La fusion crée une nouvelle instance de votre entité, copie l'état de l'entité fournie et rend la nouvelle copie gérée. L'instance que vous transmettez ne sera pas gérée (les modifications que vous apporterez ne feront pas partie de la transaction - sauf si vous appelez à nouveau fusionner).

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 existe certaines situations dans lesquelles vous voudriez utiliser le scénario 2.

1559
Mike

La persistance et la fusion ont deux objectifs différents (elles ne sont pas du tout des alternatives).

(édité pour développer l'information de différences)

persister:

  • Insérer un nouveau registre dans la base de données
  • Attachez l'objet au gestionnaire d'entités.

fusionner:

  • Trouvez un objet attaché avec le même identifiant et mettez-le à jour.
  • S'il existe, mettez à jour et retournez l'objet déjà attaché.
  • S'il n'existe pas, insérez le nouveau registre dans la base de données.

persist () efficacité:

  • Cela pourrait être plus efficace pour insérer un nouveau registre dans une base de données que merge ().
  • Cela ne duplique pas l'objet d'origine.

sémantique de persist ():

  • 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, il n’existe qu’un seul objet attaché pour n’importe quel registre du gestionnaire d’entités.

fusionner () 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 () pourrait ê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 en déduire que ce sera le cas partout.

167
Josep Panadero

Si vous utilisez le générateur affecté, tiliser la fusion au lieu de persist peut provoquer une instruction SQL redondante , affectant ainsi les performances.

En outre, fusion d'appel 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 à blanc après - effacement du contexte de persistance .

Pour comprendre comment tout cela fonctionne, vous devez d’abord savoir que Hibernate fait passer l’état de développement des instructions SQL à transitions d’état .

Une fois qu'une entité est activement gérée par Hibernate, toutes les modifications seront 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.

Tout d'abord, nous devons définir tous les états d'entité:

  • Nouveau (transitoire)

    Un objet nouvellement créé qui n’a jamais été associé à une fonction Hibernate Session (a.k.a Persistence Context) et qui n’est mappé à aucune ligne de la table de la base de données est considéré comme étant à l’état Nouveau (transitoire).

    Pour devenir persistants, nous devons soit appeler explicitement la méthode EntityManager#persist, 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 est gérée par le contexte de persistance en cours d’exécution. Toute modification apportée à cette entité sera détectée et propagée à la base de données (pendant le temps de session). Avec Hibernate, nous n’avons plus besoin d’exécuter les instructions INSERT/UPDATE/DELETE. Hibernate utilise un style de travail écriture transactionnelle et les modifications sont synchronisées au tout dernier moment responsable, pendant le Session temps actuel.

  • Détaché

    Une fois que le contexte de persistance en cours d'exécution est fermé, toutes les entités précédemment gérées sont détachées. Les modifications successives ne seront plus suivies et aucune synchronisation automatique de la base de données ne sera effectuée.

    Pour associer une entité détachée à une session Hibernate active, vous pouvez choisir l'une des options suivantes:

    • Rattachement

      Hibernate (mais pas JPA 2.1) prend en charge la reconnexion au moyen de la méthode de mise à jour de la 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 en tant que cache en mémoire (cache de premier niveau) et une seule valeur (entité) est associée à une clé donnée (type d'entité et identificateur de base de données). Une entité ne peut être rattachée que si aucun autre objet JVM (correspondant à la même ligne de la base de données) n'est déjà associé à la session Hibernate en cours.

    • Fusionner

    La fusion va copier l'état de l'entité détachée (source) dans une instance d'entité gérée (destination). Si l'entité en fusion n'a pas d'équivalent dans la session en cours, l'un d'entre eux sera extrait de la base de données. L'instance d'objet détaché continuera à rester détachée même après l'opération de fusion.

  • Enlevé

    Bien que JPA demande que seules les entités gérées soient autorisées à être supprimées, Hibernate peut également supprimer les entités détachées (mais uniquement via un appel à la méthode Session # delete). Une entité supprimée est uniquement programmée pour être supprimée et l'instruction DELETE de la base de données proprement dite sera exécutée au cours du temps de session.

Pour mieux comprendre les transitions d'état JPA, vous pouvez visualiser le diagramme suivant:

enter image description here

Ou si vous utilisez l'API spécifique d'Hibernate:

enter image description here

140
Vlad Mihalcea

J'ai remarqué que lorsque j'utilisais em.merge, j'avais une instruction SELECT pour chaque INSERT, même en l'absence de champ généré par JPA pour moi - le champ de clé primaire était un UUID. que je me suis mis. Je suis passé à em.persist(myEntityObject) et je n'ai que les déclarations INSERT à ce moment-là.

37
Sarah Vessels

La spécification JPA dit ce qui suit à propos de persist().

Si X est un objet détaché, la EntityExistsException peut être levée lorsque l'opération de persistance est appelée ou la EntityExistsException ou un autre PersistenceException peut être lancé à la chasse ou au temps imparti.

Donc, utiliser persist() serait approprié lorsque l'objet ne devrait pas ne pas être un objet détaché. Vous préférerez peut-être que le code jette 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() doit cependant avoir un objet avec le @Id déjà généré.

28
Raedwald

Il y a encore quelques différences entre merge et persist (je vais énumérer à nouveau celles déjà publiées ici):

D1. merge ne rend pas l'entité transmise gérée, mais renvoie plutôt une autre instance gérée. persist de l'autre côté rendra l'entité passée 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 pouvez le faire uniquement avec persist (), car merge lève un IllegalArgumentException.

D3. Si vous avez décidé de prendre soin de vos ID manuellement (par exemple en utilisant des UUID), une opération merge déclenchera les requêtes ultérieures SELECT afin de rechercher les entités existantes portant cet ID, tandis que persist peut ne pas avoir besoin de ces requêtes.

D4. Il existe des cas où vous ne faites simplement pas confiance au code qui appelle votre code, et pour vous assurer qu'aucune donnée n'est mise à jour, mais qu'elle est plutôt insérée, vous devez utiliser persist.

17
Andrei I

Quelques détails supplémentaires sur la fusion qui vous aideront à utiliser la fusion sur persist:

Le renvoi d'une instance gérée autre que l'entité d'origine est une partie essentielle 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é en cours de fusion, mais la version gérée qui existait déjà doit être renvoyée au client afin qu'elle puisse être utilisée. utilisé. Si le fournisseur n'a pas mis à jour l'instance Employé dans le contexte de persistance, les références à cette instance deviendront incohérentes avec le nouvel état fusionné.

Lorsque merge () est appelé sur une nouvelle entité, son comportement est similaire à celui de 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 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 qu'elle pointe vers les versions gérées des entités référencées par l'entité détachée. Si l'entité entretient une relation avec un objet sans 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 sur l'objet non persistant, tandis que d'autres peuvent lever une exception immédiatement. L'opération merge () peut éventuellement être mise en cascade dans ces cas pour empêcher la survenue d'une exception. Nous traiterons en 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é constituent un cas particulier dans l'opération de fusion. Si une relation de chargement paresseux n'a pas été déclenchée sur une entité avant qu'elle ne soit séparée, cette relation sera ignorée lors de la fusion de l'entité. Si la relation a été déclenchée pendant la gestion, puis définie sur null lors du détachement de l'entité, la version gérée de l'entité aura également la relation effacée lors de la fusion. "

Toutes les informations ci-dessus sont extraites de "Pro JPA 2: maîtriser l’API de persistance Java ™" de Mike Keith et Merrick Schnicariol. Chapitre 6. Détachement de section et fusion. Ce livre est en fait un deuxième livre consacré à JPA par des auteurs. Ce nouveau livre contient de nombreuses nouvelles informations, puis les anciennes. Je recommande vraiment de lire ce livre pour ceux qui seront sérieusement impliqués dans l'APP. Je suis désolé d'avoir posté ma première réponse de manière anonyme.

16
Khurshed Salimov

J'obtenais des exceptions lazyLoading sur mon entité parce que j'essayais d'accéder à une collection chargée paresseuse en session.

Dans une requête distincte, je récupérais l'entité de la session, puis j'essayais d'accéder à une collection de ma page jsp, ce qui posait problème.

Pour atténuer cela, j'ai mis à jour la même entité dans mon contrôleur et je l'ai transmise à mon jsp, bien que j'imagine que lorsque je le réenregistre en session, il sera également accessible via SessionScope et ne jettera pas 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!!
8
logixplayer

En parcourant les réponses, il manque quelques détails concernant la génération de Cascade et id. Voir question

Il est également intéressant de noter que vous pouvez avoir des annotations Cascade distinctes pour la fusion et la persistance: Cascade.MERGE et Cascade.PERSIST qui seront traitées selon la méthode utilisée.

La spec est votre ami;)

6
Ioannis Deligiannis

J'ai trouvé cette explication enrichissante dans la documentation d'Hibernate, car elles contiennent un cas d'utilisation:

L'utilisation et la sémantique de merge () semblent déroutantes pour les nouveaux utilisateurs. Tout d'abord, 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 utiliser la méthode 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 passé à la couche de présentation
  • quelques modifications sont apportées à l'objet
  • l'objet est redirigé vers la couche de 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 le charger à partir de 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 rejetée

De: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html

6
Ray Hulha

JPA représente indiscutablement 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 complexités des anciens beans entity dans J2EE, je considère l'inclusion de JPA parmi les spécifications Java EE comme un grand pas en avant. Cependant, 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 qui se chevauche risque de semer la confusion chez un débutant. De plus, je propose une généralisation qui considère les deux méthodes comme des cas particuliers d’une méthode plus générale.

Entités persistantes

Contrairement à la méthode de fusion, la méthode persist est assez simple et intuitive. Le scénario le plus courant d'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 être insérée 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. "

enter image description here

Les spécifications vont plus dans les détails, cependant, les rappeler n'est pas crucial car ces détails couvrent seulement des situations plus ou moins exotiques.

Fusion d'entités

En comparaison, la description du comportement de la fusion n’est pas aussi simple. Il n'y a pas de scénario principal, comme c'est le cas avec persist, et un programmeur doit se souvenir de tous les scénarios afin d'écrire un code correct. Il me semble que les concepteurs de JPA souhaitaient une méthode dont la préoccupation principale serait la gestion des entités détachées (au contraire de la méthode persist 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 (transmise en tant qu'argument) à son homologue gérée dans le contexte de persistance. Cette tâche se divise toutefois 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 de flux décrivant schématiquement le comportement de la méthode de fusion:

enter image description here

Alors, quand devrais-je utiliser persister et quand fusionner?

persiste

  • Vous voulez que la méthode crée toujours une nouvelle entité et ne met jamais à jour une entité. Sinon, la méthode lève une exception à la suite d'une violation d'unicité de la clé primaire.
  • Traitement par lots, gestion des entités de manière dynamique (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 les entités de manière sans état (objets de transfert de données dans les services)
  • Vous souhaitez insérer une nouvelle entité pouvant faire référence à une autre entité mais qui ne peut pas encore être créée (la relation doit être marquée MERGE). Par exemple, insérer une nouvelle photo avec une référence à un nouvel album ou à un album déjà existant.
6
Amit Gujarathi

Scénario X:

Table: Spitter (One), Table: Spittles (Many) (Spittles est le propriétaire de la relation avec un FK: spitter_id)

Ce scénario permet d'économiser: The Spitter et les deux Spittles comme s'ils appartenaient à Same 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, les 2 Spittles, 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!!
5

Autre observation:

merge() ne se soucie que d'un identifiant généré automatiquement (testé sur IDENTITY et SEQUENCE) lorsqu'un enregistrement avec un tel identifiant existe déjà dans votre table. Dans ce cas, merge() tentera de mettre à jour l'enregistrement. Si, toutefois, un identifiant est absent ou ne correspond à aucun enregistrement existant, merge() l'ignorera complètement et demandera à une base de disque d'en allouer un nouveau. C'est parfois une source de nombreux bugs. N'utilisez pas merge() pour forcer un identifiant pour un nouvel enregistrement.

persist() d'un autre côté ne vous laissera jamais même lui passer un identifiant. Cela va échouer immédiatement. Dans mon cas, c'est:

Causée par: org.hibernate.PersistentObjectException: entité détachée passée à persister

hibernate-jpa javadoc a un indice:

Lance: javax.persistence.EntityExistsException - si l'entité existe déjà. (Si l'entité existe déjà, l'entité EntityExistsException peut être levée lorsque l'opération de persistance est invoquée, ou l'entité EntityExistsException ou une autre exception PersistenceException peut être levée au moment de la purge ou de la validation.)

3
yuranos87

Vous êtes peut-être venu ici pour savoir quand utiliser persister et quand utiliser fusionner. Je pense que cela dépend de la situation: quelle est la probabilité que vous ayez besoin de créer un nouvel enregistrement et la difficulté de récupérer des données persistantes.

Supposons que vous puissiez utiliser une clé/identifiant naturel.

  • Les données doivent être conservées, mais de temps en temps, un enregistrement existe et une mise à jour est demandée. Dans ce cas, vous pouvez essayer une persistance et si elle lève une EntityExistsException, vous la recherchez et combinez les données:

    try {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 regardez et faites une persistante si l'entité est manquante:

    entity = entityManager.find (clé);

    if (entity == null) {entityManager.persist (entity); }

    else {/ * merge * /}

Si vous n'avez pas de clé/identifiant 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:

  1. Si les modifications sont généralement petites, appliquez-les à l'entité gérée.
  2. Si les modifications sont courantes, copiez l'ID de l'entité persistante, ainsi que les données non modifiées. Ensuite, appelez EntityManager :: merge () pour remplacer l'ancien contenu.
1
Peter Willems

persist (entité) 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 une entité EntityExistsException).

fusionner (entité) doit être utilisé pour remettre l'entité en contexte de persistance si l'entité a été détachée et modifiée.

Persister probablement générer une instruction INSERT SQL et fusionner une instruction UPDATE SQL (mais je ne suis pas sûr).

0
Krystian