Quelles sont les différences entre les différentes méthodes d'enregistrement dans Hibernate?

199

Hibernate a une poignée de méthodes qui, d'une manière ou d'une autre, prennent votre objet et le mettent dans la base de données. Quelles sont les différences entre eux, quand les utiliser et pourquoi n'y a-t-il pas une seule méthode intelligente qui sait quand utiliser quoi?

Les méthodes que j'ai identifiées jusqu'à présent sont:

  • save()
  • update()
  • saveOrUpdate()
  • saveOrUpdateCopy()
  • merge()
  • persist()
Henrik Paul
la source

Réponses:

117

Voici ma compréhension des méthodes. Ils sont principalement basés sur l' API, car je ne les utilise pas tous dans la pratique.

saveOrUpdate Les appels sont enregistrés ou mis à jour en fonction de certaines vérifications. Par exemple, si aucun identifiant n'existe, save est appelé. Sinon, la mise à jour est appelée.

save Persiste une entité. Attribuera un identifiant s'il n'en existe pas. Si tel est le cas, il s'agit essentiellement d'une mise à jour. Renvoie l'ID généré de l'entité.

update Tente de conserver l'entité à l'aide d'un identifiant existant. Si aucun identifiant n'existe, je pense qu'une exception est levée.

saveOrUpdateCopy Ceci est obsolète et ne devrait plus être utilisé. Au lieu de cela, il y a ...

fusionner C'est maintenant que ma connaissance commence à faiblir. L'important ici est la différence entre les entités transitoires, détachées et persistantes. Pour plus d'informations sur les états des objets, jetez un œil ici . Avec save & update, vous traitez avec des objets persistants. Ils sont liés à une session afin qu'Hibernate sache ce qui a changé. Mais lorsque vous avez un objet transitoire, aucune session n'est impliquée. Dans ces cas, vous devez utiliser la fusion pour les mises à jour et persister pour l'enregistrement.

persist Comme mentionné ci-dessus, il est utilisé sur les objets transitoires. Il ne renvoie pas l'ID généré.

Lee Theobald
la source
22
Je voudrais accepter cela comme réponse, mais une chose n'est toujours pas claire: puisque save () retombe sur update (), si un tel élément existe, en quoi diffère-t-il de saveOrUpdate () dans la pratique?
Henrik Paul
Où est-il spécifié que cette sauvegarde fonctionnerait sur des instances détachées?
jrudolph
2
Si votre description de la fusion / persistance n'est importante que sur les objets transitoires, cela a beaucoup de sens et correspond à la façon dont nous utilisons la mise en veille prolongée. Notez également qu'une fusion a souvent des limitations de performances par rapport à une mise à jour car elle semble faire une récupération supplémentaire pour les vérifications d'intégrité de quelque sorte.
Martin Dale Lyness
1
La réponse de jrudolph ci-dessous est plus précise.
azerole
2
Étant donné qu'hibernate sait probablement dans quel état se trouve un objet, pourquoi devons-nous le faire manuellement lors de l'écriture du programme. Il ne devrait y avoir qu'une seule méthode de sauvegarde.
masterxilo
116
╔══════════════╦═══════════════════════════════╦════════════════════════════════╗
    METHOD                TRANSIENT                      DETACHED            
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                     sets id if doesn't         sets new id even if object   
    save()         exist, persists to db,        already has it, persists    
                  returns attached object     to DB, returns attached object 
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                     sets id on object                    throws             
   persist()       persists object to DB            PersistenceException     
                                                                             
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                                                                             
   update()              Exception                persists and reattaches    
                                                                             
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                copy the state of object in      copy the state of obj in    
    merge()        DB, doesn't attach it,    ║      DB, doesn't attach it,    
                  returns attached object         returns attached object    
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                                                                             
saveOrUpdate()║           as save()                       as update()         
                                                                             
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝
Sergii Shevchyk
la source
updateun objet transitoire est très bien, je n'ai pas eu d'exception.
GMsoF
Ce que je sais, nous ne pouvons pas persister un transitoire dans les deux sens. Je pense que la différence pourrait être entre détaché et persistant. S'il vous plaît corrigez-moi.
Ram
il y a beaucoup d'erreurs ici ... par exemple 1) ´save () ´ ne retourne pas un "objet attaché", il renvoie l'´id´; 2) ´persist () ´ n'est pas garanti pour définir le ´id´, ni "persiste l'objet à DB"; ...
Eugen Labun
67
  • Voir le Forum Hibernate pour une explication des différences subtiles entre persist et save. Il semble que la différence soit le moment où l'instruction INSERT est finalement exécutée. Étant donné que save renvoie l'identifiant, l'instruction INSERT doit être exécutée instantanément quel que soit l'état de la transaction (ce qui est généralement une mauvaise chose). Persist n'exécutera aucune instruction en dehors de la transaction en cours d'exécution uniquement pour attribuer l'identifiant. Save / Persist fonctionnent tous les deux sur des instances transitoires , c'est-à-dire des instances auxquelles aucun identifiant n'est encore attribué et en tant que telles ne sont pas enregistrées dans la base de données.

  • La mise à jour et la fusion fonctionnent toutes les deux sur des instances détachées , c'est-à-dire des instances qui ont une entrée correspondante dans la base de données mais qui ne sont actuellement pas attachées (ou gérées par) une session. La différence entre eux est ce qui arrive à l'instance qui est passée à la fonction. update essaie de rattacher l'instance, cela signifie qu'il ne doit pas y avoir d'autre instance de l'entité persistante attachée à la session pour le moment, sinon une exception est levée. la fusion , cependant, copie simplement toutes les valeurs dans une instance persistante de la session (qui sera chargée si elle n'est pas actuellement chargée). L'objet d'entrée n'est pas modifié. La fusion est donc plus générale que la mise à jour, mais peut utiliser plus de ressources.

jrudolph
la source
l'insertion d'une instruction ne se produit toujours pas si vous avez votre propre générateur d'ID
kommradHomer
Donc, en cas d'objet détaché, la fusion déclenchera une sélection alors que la mise à jour ne le sera pas?
Gab
1
save() - If an INSERT has to be executed to get the identifier, then this INSERT happens immediately, no matter if you are inside or outside of a transaction. This is problematic in a long-running conversation with an extended Session/persistence context.Pouvez-vous s'il vous plaît me dire comment un insert peut se produire en dehors d'une session et pourquoi est-ce mauvais?
Erran Morad
Avertissement: je n'ai pas utilisé hibernate depuis longtemps. OMI, le problème est le suivant: la signature et le contrat de save () exigent que save renvoie un identifiant pour le nouvel objet. Selon la stratégie de génération d'ID que vous choisissez, l'identifiant est généré par la base de données lorsqu'une valeur est INSERTéditée. Par conséquent, dans ces cas, vous ne pouvez pas renvoyer un identifiant maintenant sans l'avoir généré et pour le générer, vous devez exécuter INSERT maintenant . Depuis, une transaction de longue durée n'est pas exécutée en ce moment mais uniquement lors de la validation, la seule façon d'exécuter le INSERTmaintenant est de l'exécuter en dehors du tx.
jrudolph
12

Ce lien explique bien:

http://www.stevideter.com/2008/12/07/saveorupdate-versus-merge-in-hibernate/

Nous avons tous ces problèmes que nous rencontrons assez rarement pour que lorsque nous les revoyons, nous savons que nous avons résolu ce problème, mais je ne me souviens pas comment.

La NonUniqueObjectException levée lors de l'utilisation de Session.saveOrUpdate () dans Hibernate est l'une des miennes. Je vais ajouter de nouvelles fonctionnalités à une application complexe. Tous mes tests unitaires fonctionnent bien. Puis, en testant l'interface utilisateur, en essayant d'enregistrer un objet, je commence à obtenir une exception avec le message "un objet différent avec la même valeur d'identifiant était déjà associé à la session." Voici un exemple de code de Java Persistence avec Hibernate.

            Session session = sessionFactory1.openSession();
            Transaction tx = session.beginTransaction();
            Item item = (Item) session.get(Item.class, new Long(1234));
            tx.commit();
            session.close(); // end of first session, item is detached

            item.getId(); // The database identity is "1234"
            item.setDescription("my new description");
            Session session2 = sessionFactory.openSession();
            Transaction tx2 = session2.beginTransaction();
            Item item2 = (Item) session2.get(Item.class, new Long(1234));
            session2.update(item); // Throws NonUniqueObjectException
            tx2.commit();
            session2.close();

Pour comprendre la cause de cette exception, il est important de comprendre les objets détachés et ce qui se passe lorsque vous appelez saveOrUpdate () (ou simplement update ()) sur un objet détaché.

Lorsque nous fermons une session Hibernate individuelle, les objets persistants avec lesquels nous travaillons sont détachés. Cela signifie que les données sont toujours dans la mémoire de l'application, mais Hibernate n'est plus responsable du suivi des modifications apportées aux objets.

Si nous modifions ensuite notre objet détaché et que nous voulons le mettre à jour, nous devons rattacher l'objet. Au cours de ce processus de rattachement, Hibernate vérifiera s'il existe d'autres copies du même objet. S'il en trouve, il doit nous dire qu'il ne sait plus ce qu'est la «vraie» copie. Peut-être que d'autres modifications ont été apportées à ces autres copies que nous prévoyons d'enregistrer, mais Hibernate ne les connaît pas, car il ne les gérait pas à l'époque.

Plutôt que d'enregistrer des données potentiellement mauvaises, Hibernate nous parle du problème via l'exception NonUniqueObjectException.

Alors, que devons-nous faire? Dans Hibernate 3, nous avons merge () (dans Hibernate 2, utilisez saveOrUpdateCopy ()). Cette méthode force Hibernate à copier toutes les modifications d'autres instances détachées sur l'instance que vous souhaitez enregistrer, et fusionne donc toutes les modifications en mémoire avant l'enregistrement.

        Session session = sessionFactory1.openSession();
        Transaction tx = session.beginTransaction();
        Item item = (Item) session.get(Item.class, new Long(1234));
        tx.commit();
        session.close(); // end of first session, item is detached

        item.getId(); // The database identity is "1234"
        item.setDescription("my new description");
        Session session2 = sessionFactory.openSession();
        Transaction tx2 = session2.beginTransaction();
        Item item2 = (Item) session2.get(Item.class, new Long(1234));
        Item item3 = session2.merge(item); // Success!
        tx2.commit();
        session2.close();

Il est important de noter que la fusion renvoie une référence à la nouvelle version mise à jour de l'instance. Il ne s'agit pas de rattacher un élément à la session. Si vous testez l'égalité d'instance (item == item3), vous constaterez qu'elle retourne false dans ce cas. Vous voudrez probablement travailler avec l'élément3 à partir de ce point.

Il est également important de noter que l'API Java Persistence (JPA) n'a pas de concept d'objets détachés et réattachés et utilise EntityManager.persist () et EntityManager.merge ().

J'ai trouvé en général que lorsque vous utilisez Hibernate, saveOrUpdate () est généralement suffisant pour mes besoins. Je n'ai généralement besoin d'utiliser la fusion que lorsque j'ai des objets qui peuvent faire référence à des objets du même type. Plus récemment, la cause de l'exception était dans le code validant que la référence n'était pas récursive. Je chargeais le même objet dans ma session dans le cadre de la validation, provoquant l'erreur.

Où avez-vous rencontré ce problème? La fusion a-t-elle fonctionné pour vous ou avez-vous eu besoin d'une autre solution? Préférez-vous toujours utiliser la fusion, ou préférez-vous l'utiliser uniquement selon les besoins pour des cas spécifiques

Hakuna Matata
la source
Lien vers l'article sur webarchive, car l'original n'est pas disponible: web.archive.org/web/20160521091122/http://www.stevideter.com:80/…
Eugen Labun
5

En fait, la différence entre la mise en veille prolongée save()et les persist()méthodes dépend de la classe de générateur que nous utilisons.

Si notre classe de générateur est affectée, il n'y a aucune différence entre les méthodes save()et persist(). Parce que le générateur «assigné» signifie, en tant que programmeur, nous devons donner la valeur de clé primaire à enregistrer dans la bonne base de données [J'espère que vous connaissez ce concept de générateur] Dans le cas d'une classe de générateur autre que celle affectée, supposez que si le nom de notre classe de générateur est Increment signifie hibernate it self assignera la valeur de l'identifiant de la clé primaire dans le droit de la base de données [autre que le générateur assigné, hibernate n'est utilisé que pour prendre soin de la valeur de l'identifiant de la clé primaire rappelez-vous], donc dans ce cas, si nous appelons save()ou la persist()méthode, il insérera l'enregistrement dans la base de données normalement Mais écoutez, la save()méthode peut retourner cette valeur d'ID de clé primaire qui est générée par hibernate et nous pouvons la voir par

long s = session.save(k);

Dans ce même cas, persist()ne restituera jamais aucune valeur au client.

Hari Krishna
la source
5

J'ai trouvé un bon exemple montrant les différences entre toutes les méthodes de sauvegarde en veille prolongée:

http://www.journaldev.com/3481/hibernate-session-merge-vs-update-save-saveorupdate-persist-example

En bref, selon le lien ci-dessus:

enregistrer()

  • Nous pouvons invoquer cette méthode en dehors d'une transaction. Si nous l'utilisons sans transaction et que nous avons une cascade entre les entités, seule l'entité principale est enregistrée, sauf si nous vidons la session.
  • Ainsi, s'il existe d'autres objets mappés à partir de l'objet principal, ils sont enregistrés au moment de la validation de la transaction ou lorsque nous vidons la session.

persister()

  • C'est similaire à l'utilisation de save () dans la transaction, donc c'est sûr et prend soin de tous les objets en cascade.

saveOrUpdate ()

  • Peut être utilisé avec ou sans la transaction, et tout comme save (), s'il est utilisé sans la transaction, les entités mappées ne seront pas enregistrées sans; nous vidons la session.

  • Résultats dans les requêtes d'insertion ou de mise à jour en fonction des données fournies. Si les données sont présentes dans la base de données, une requête de mise à jour est exécutée.

mettre à jour()

  • La mise à jour Hibernate doit être utilisée lorsque nous savons que nous ne mettons à jour que les informations sur l'entité. Cette opération ajoute l'objet entité au contexte persistant et les autres modifications sont suivies et enregistrées lorsque la transaction est validée.
  • Par conséquent, même après avoir appelé update, si nous définissons des valeurs dans l'entité, elles seront mises à jour lorsque la transaction est validée.

fusionner()

  • La fusion en veille prolongée peut être utilisée pour mettre à jour les valeurs existantes, mais cette méthode crée une copie à partir de l'objet entité passé et la renvoie. L'objet retourné fait partie du contexte persistant et est suivi pour toutes les modifications, l'objet transmis n'est pas suivi. C'est la principale différence avec merge () de toutes les autres méthodes.

Aussi pour des exemples pratiques de tout cela, veuillez vous référer au lien que j'ai mentionné ci-dessus, il montre des exemples pour toutes ces différentes méthodes.

Hors de l'esprit
la source
3

Comme je l'ai expliqué dans cet article , vous devriez préférer les méthodes JPA la plupart du temps et les updatetâches de traitement par lots.

Une entité JPA ou Hibernate peut se trouver dans l'un des quatre états suivants:

  • Transitoire (nouveau)
  • Géré (persistant)
  • Détaché
  • Supprimé (supprimé)

La transition d'un état à l'autre se fait via les méthodes EntityManager ou Session.

Par exemple, le JPA EntityManagerfournit les méthodes de transition d'état d'entité suivantes.

entrez la description de l'image ici

Hibernate Sessionimplémente toutes les EntityManagerméthodes JPA et fournit des méthodes de transition d'état d'entité supplémentaires comme save, saveOrUpdateet update.

entrez la description de l'image ici

Persister

Pour changer l'état d'une entité de Transitoire (Nouveau) à Géré (Persistant), nous pouvons utiliser la persistméthode proposée par le JPA EntityManagerqui est également héritée par l'Hibernate Session.

La persistméthode déclenche un PersistEventqui est géré par l' DefaultPersistEventListenerécouteur d'événements Hibernate.

Par conséquent, lors de l'exécution du scénario de test suivant:

doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    LOGGER.info(
        "Persisting the Book entity with the id: {}", 
        book.getId()
    );
});

Hibernate génère les instructions SQL suivantes:

CALL NEXT VALUE FOR hibernate_sequence

-- Persisting the Book entity with the id: 1

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

Notez que le idest attribué avant d'attacher l' Bookentité au contexte de persistance actuel. Cela est nécessaire car les entités gérées sont stockées dans une Mapstructure où la clé est formée par le type d'entité et son identifiant et la valeur est la référence d'entité. C'est la raison pour laquelle le JPA EntityManageret le Hibernate Sessionsont connus comme le cache de premier niveau.

Lors de l'appel persist, l'entité est uniquement attachée au contexte de persistance en cours d'exécution et l'insertion peut être différée jusqu'à ce que l' flushappel soit effectué.

La seule exception est le générateur IDENTITY qui déclenche immédiatement INSERT car c'est la seule façon d'obtenir l'identifiant d'entité. Pour cette raison, Hibernate ne peut pas traiter par lots des insertions pour les entités utilisant le générateur IDENTITY. Pour plus de détails sur ce sujet, consultez cet article .

sauver

La saveméthode spécifique à Hibernate est antérieure à JPA et elle est disponible depuis le début du projet Hibernate.

La saveméthode déclenche un SaveOrUpdateEventqui est géré par l' DefaultSaveOrUpdateEventListenerécouteur d'événements Hibernate. Par conséquent, la saveméthode est équivalente aux méthodes updateet saveOrUpdate.

Pour voir comment fonctionne la saveméthode, considérez le cas de test suivant:

doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);

    Long id = (Long) session.save(book);

    LOGGER.info(
        "Saving the Book entity with the id: {}", 
        id
    );
});

Lors de l'exécution du scénario de test ci-dessus, Hibernate génère les instructions SQL suivantes:

CALL NEXT VALUE FOR hibernate_sequence

-- Saving the Book entity with the id: 1

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

Comme vous pouvez le voir, le résultat est identique à l' persistappel de méthode. Cependant, contrairement à persist, la saveméthode renvoie l'identifiant d'entité.

Pour plus de détails, consultez cet article .

Mettre à jour

La updateméthode spécifique à Hibernate est destinée à contourner le mécanisme de vérification incorrecte et à forcer une mise à jour d'entité au moment du vidage.

La updateméthode déclenche un SaveOrUpdateEventqui est géré par l' DefaultSaveOrUpdateEventListenerécouteur d'événements Hibernate. Par conséquent, la updateméthode est équivalente aux méthodes saveet saveOrUpdate.

Pour voir comment fonctionne la updateméthode, considérez l'exemple suivant qui persiste une Bookentité dans une transaction, puis il la modifie pendant que l'entité est à l'état détaché et force la mise à jour SQL à l'aide de l' updateappel de méthode.

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

LOGGER.info("Modifying the Book entity");

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);

    session.update(_book);

    LOGGER.info("Updating the Book entity");
});

Lors de l'exécution du scénario de test ci-dessus, Hibernate génère les instructions SQL suivantes:

CALL NEXT VALUE FOR hibernate_sequence

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

-- Modifying the Book entity
-- Updating the Book entity

UPDATE 
    book 
SET 
    author = 'Vlad Mihalcea', 
    isbn = '978-9730228236', 
    title = 'High-Performance Java Persistence, 2nd edition'
WHERE 
    id = 1

Notez que le UPDATEest exécuté pendant le vidage du contexte de persistance, juste avant la validation, et c'est pourquoi le Updating the Book entitymessage est enregistré en premier.

Utilisation @SelectBeforeUpdatepour éviter les mises à jour inutiles

Maintenant, la MISE À JOUR va toujours être exécutée même si l'entité n'a pas été modifiée lorsqu'elle était à l'état détaché. Pour éviter cela, vous pouvez utiliser l' @SelectBeforeUpdateannotation Hibernate qui déclenchera une SELECTinstruction récupérée loaded statequi sera ensuite utilisée par le mécanisme de vérification sale.

Donc, si nous annotons l' Bookentité avec l' @SelectBeforeUpdateannotation:

@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {

    //Code omitted for brevity
}

Et exécutez le scénario de test suivant:

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);

    session.update(_book);
});

Hibernate exécute les instructions SQL suivantes:

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

SELECT 
    b.id,
    b.author AS author2_0_,
    b.isbn AS isbn3_0_,
    b.title AS title4_0_
FROM 
    book b
WHERE 
    b.id = 1

Notez que, cette fois, il n'y a pas d' UPDATEexécutions puisque le mécanisme de vérification sale Hibernate a détecté que l'entité n'a pas été modifiée.

SaveOrUpdate

La saveOrUpdateméthode spécifique à Hibernate n'est qu'un alias pour saveet update.

La saveOrUpdateméthode déclenche un SaveOrUpdateEventqui est géré par l' DefaultSaveOrUpdateEventListenerécouteur d'événements Hibernate. Par conséquent, la updateméthode est équivalente aux méthodes saveet saveOrUpdate.

Maintenant, vous pouvez utiliser saveOrUpdatelorsque vous souhaitez conserver une entité ou forcer une UPDATEcomme illustré par l'exemple suivant.

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(book);

    return book;
});

_book.setTitle("High-Performance Java Persistence, 2nd edition");

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(_book);
});

Méfiez-vous des NonUniqueObjectException

Un problème qui peut se produire avec save, updateet saveOrUpdateest si le contexte de persistance contient déjà une référence d'entité avec le même ID et du même type que dans l'exemple suivant:

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(book);

    return book;
});

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

try {
    doInJPA(entityManager -> {
        Book book = entityManager.find(
            Book.class, 
            _book.getId()
        );

        Session session = entityManager.unwrap(Session.class);
        session.saveOrUpdate(_book);
    });
} catch (NonUniqueObjectException e) {
    LOGGER.error(
        "The Persistence Context cannot hold " +
        "two representations of the same entity", 
        e
    );
}

Maintenant, lors de l'exécution du scénario de test ci-dessus, Hibernate va lancer un NonUniqueObjectExceptioncar le second EntityManagercontient déjà une Bookentité avec le même identifiant que celui vers lequel nous passons update, et le contexte de persistance ne peut pas contenir deux représentations de la même entité.

org.hibernate.NonUniqueObjectException: 
    A different object with the same identifier value was already associated with the session : [com.vladmihalcea.book.hpjp.hibernate.pc.Book#1]
    at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:651)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
    at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:682)
    at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:674)

Fusionner

Pour éviter cela NonUniqueObjectException, vous devez utiliser la mergeméthode proposée par la JPA EntityManageret héritée également par l'Hibernate Session.

Comme expliqué dans cet article , le mergerécupère un nouvel instantané d'entité de la base de données s'il n'y a pas de référence d'entité trouvée dans le contexte de persistance, et il copie l'état de l'entité détachée passée à la mergeméthode.

La mergeméthode déclenche un MergeEventqui est géré par l' DefaultMergeEventListenerécouteur d'événements Hibernate.

Pour voir comment fonctionne la mergeméthode, considérez l'exemple suivant qui persiste une Bookentité dans une transaction, puis il la modifie pendant que l'entité est à l'état détaché et passe l'entité détachée à mergedans un contexte de persistance de sous-séquence.

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

LOGGER.info("Modifying the Book entity");

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

doInJPA(entityManager -> {
    Book book = entityManager.merge(_book);

    LOGGER.info("Merging the Book entity");

    assertFalse(book == _book);
});

Lors de l'exécution du scénario de test ci-dessus, Hibernate a exécuté les instructions SQL suivantes:

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

-- Modifying the Book entity

SELECT 
    b.id,
    b.author AS author2_0_,
    b.isbn AS isbn3_0_,
    b.title AS title4_0_
FROM 
    book b
WHERE 
    b.id = 1

-- Merging the Book entity

UPDATE 
    book 
SET 
    author = 'Vlad Mihalcea', 
    isbn = '978-9730228236', 
    title = 'High-Performance Java Persistence, 2nd edition'
WHERE 
    id = 1

Notez que la référence d'entité renvoyée par mergeest différente de celle détachée que nous avons passée à la mergeméthode.

Maintenant, bien que vous préfériez utiliser JPA mergelors de la copie de l'état d'entité détachée, le supplément SELECTpeut être problématique lors de l'exécution d'une tâche de traitement par lots.

Pour cette raison, vous devriez préférer utiliser updatelorsque vous êtes sûr qu'aucune référence d'entité n'est déjà attachée au contexte de persistance en cours d'exécution et que l'entité détachée a été modifiée.

Pour plus de détails sur ce sujet, consultez cet article .

Conclusion

Pour conserver une entité, vous devez utiliser la persistméthode JPA . Pour copier l'état de l'entité détachée, mergedoit être préféré. La updateméthode est utile pour les tâches de traitement par lots uniquement. Les saveet ne saveOrUpdatesont que des alias updateet vous ne devriez probablement pas les utiliser du tout.

Certains développeurs appellent savemême lorsque l'entité est déjà gérée, mais c'est une erreur et déclenche un événement redondant car, pour les entités gérées, la MISE À JOUR est automatiquement gérée au moment du vidage du contexte de persistance.

Pour plus de détails, consultez cet article .

Vlad Mihalcea
la source
2

Sachez que si vous appelez une mise à jour sur un objet détaché, une mise à jour sera toujours effectuée dans la base de données, que vous ayez modifié l'objet ou non. Si ce n'est pas ce que vous voulez, vous devez utiliser Session.lock () avec LockMode.None.

Vous ne devez appeler la mise à jour que si l'objet a été modifié en dehors de la portée de votre session actuelle (en mode détaché).

bernardn
la source
1

Aucune des réponses suivantes n'est correcte. Toutes ces méthodes semblent être similaires, mais en pratique, elles font des choses absolument différentes. Il est difficile de faire de brefs commentaires. Mieux vaut donner un lien vers une documentation complète sur ces méthodes: http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html

Anton Popovich
la source
11
Veuillez nous expliquer pourquoi les réponses suivantes ne sont pas correctes.
Erran Morad
0

Aucune des réponses ci-dessus n'est complète. Bien que la réponse de Leo Theobald ressemble à la réponse la plus proche.

Le point fondamental est de savoir comment hibernate traite les états des entités et comment il les gère en cas de changement d'état. Tout doit être vu en ce qui concerne les bouffées de chaleur et les commits également, ce que tout le monde semble avoir complètement ignoré.

N'UTILISEZ JAMAIS LA MÉTHODE SAUVEGARDE D'HIBERNATION. OUBLIEZ QU'IL EXISTE MÊME EN HIBERNATION!

Persister

Comme tout le monde l'a expliqué, Persist fait essentiellement la transition d'une entité de l'état "Transitoire" à l'état "Géré". À ce stade, un slush ou un commit peut créer une instruction d'insertion. Mais l'entité restera toujours dans l'état "Géré". Cela ne change pas avec flush.

À ce stade, si vous "Persistez" à nouveau, il n'y aura aucun changement. Et il n'y aura plus d'économies si nous essayons de faire persister une entité persistante.

Le plaisir commence lorsque nous essayons d'expulser l'entité.

Une expulsion est une fonction spéciale d'Hibernate qui fera passer l'entité de "Gérée" à "Détachée". Nous ne pouvons pas appeler un persist sur une entité détachée. Si nous le faisons, Hibernate déclenche une exception et la transaction entière est annulée lors de la validation.

Fusion vs mise à jour

Ce sont 2 fonctions intéressantes qui font des choses différentes lorsqu'elles sont traitées de différentes manières. Tous deux tentent de faire passer l'entité de l'état "Détaché" à l'état "Géré". Mais le faire différemment.

Comprenez le fait que Détaché signifie une sorte d'état "hors ligne". et géré signifie état "En ligne".

Observez le code ci-dessous:

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();

    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    ses1.merge(entity);

    ses1.delete(entity);

    tx1.commit();

Quand tu fais ça? Que penses-tu qu'il va se passer? Si vous avez dit que cela déclencherait une exception, alors vous avez raison. Cela déclenchera une exception car, la fusion a fonctionné sur l'objet entité, qui est l'état détaché. Mais cela ne modifie pas l'état de l'objet.

Dans les coulisses, la fusion déclenche une requête de sélection et renvoie essentiellement une copie de l'entité qui est dans un état attaché. Observez le code ci-dessous:

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();
    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    HibEntity copied = (HibEntity)ses1.merge(entity);
    ses1.delete(copied);

    tx1.commit();

L'exemple ci-dessus fonctionne parce que la fusion a amené une nouvelle entité dans le contexte qui est dans un état persistant.

Lorsqu'il est appliqué avec Update, le même fonctionne bien car la mise à jour n'apporte pas réellement une copie d'entité comme la fusion.

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();

    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    ses1.update(entity);

    ses1.delete(entity);

    tx1.commit();

Dans le même temps, dans la trace de débogage, nous pouvons voir que Update n'a pas soulevé de requête SQL de sélection comme fusion.

supprimer

Dans l'exemple ci-dessus, j'ai utilisé la suppression sans parler de la suppression. La suppression fera essentiellement passer l'entité de l'état géré à l'état "supprimé". Et lorsqu'elle est vidée ou validée, elle émet une commande de suppression à stocker.

Cependant, il est possible de ramener l'entité à l'état "géré" de l'état "supprimé" à l'aide de la méthode persist.

J'espère que l'explication ci-dessus a clarifié tous les doutes.

Bière de gingembre
la source