Comme il s'agit d'une question très courante, j'ai écrit
cet article , sur lequel cette réponse est basée.
États d'entité
JPA définit les états d'entité suivants:
Nouveau (transitoire)
Un objet nouvellement créé qui n'a jamais été associé à un Hibernate Session
(aka Persistence Context
) et n'est mappé à aucune ligne de table de base de données est considéré comme étant dans l'état Nouveau (transitoire).
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 la session).
Avec Hibernate, nous n'avons plus besoin d'exécuter des 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 sont détachées. Les modifications successives ne seront plus suivies et aucune synchronisation automatique de la base de données ne se produira.
Transitions d'état d'entité
Vous pouvez modifier l'état de l'entité à l'aide de diverses méthodes définies par l' EntityManager
interface.
Pour mieux comprendre les transitions d'état des entités JPA, considérez le diagramme suivant:
Lorsque vous utilisez JPA, pour réassocier une entité détachée à un actif EntityManager
, vous pouvez utiliser l' opération de fusion .
Lors de l'utilisation de l'API Hibernate native merge
, vous pouvez également rattacher une entité détachée à une session Hibernate active à l'aide des méthodes de mise à jour, comme le montre le diagramme suivant:
Fusionner une entité détachée
La fusion va copier l'état de l'entité détachée (source) vers une instance d'entité gérée (destination).
Considérez que nous avons persisté l' Book
entité suivante , et maintenant l'entité est détachée car celle EntityManager
qui a été utilisée pour conserver l'entité s'est fermée:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
Pendant que l'entité est à l'état détaché, nous la modifions comme suit:
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
Maintenant, nous voulons propager les modifications dans la base de données, afin que nous puissions appeler la merge
méthode:
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
Et Hibernate va exécuter les instructions SQL suivantes:
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
Si l'entité fusionnée n'a pas d'équivalent dans le courant EntityManager
, un instantané d'entité frais sera extrait de la base de données.
Une fois qu'il y a une entité gérée, JPA copie l'état de l'entité détachée sur celle qui est actuellement gérée, et pendant le contexte de persistanceflush
, une MISE À JOUR sera générée si le mécanisme de vérification sale trouve que l'entité gérée a changé.
Ainsi, lors de l'utilisation merge
, l'instance d'objet détaché continuera à rester détachée même après l'opération de fusion.
Rattacher une entité détachée
Hibernate, mais pas JPA, prend en charge la reconnexion via la update
méthode.
Un Hibernate Session
ne peut associer qu'un seul objet entité pour une ligne de base de données donnée. Cela est dû au fait que le contexte de persistance agit comme un cache en mémoire (cache de premier niveau) et qu'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 s'il n'y a pas d'autre objet JVM (correspondant à la même ligne de base de données) déjà associé au Hibernate actuel Session
.
Considérant que nous avons persisté l' Book
entité et que nous l'avons modifiée lorsque l' Book
entité était à l'état détaché:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
Nous pouvons rattacher l'entité détachée comme ceci:
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
LOGGER.info("Updating the Book entity");
});
Et Hibernate exécutera l'instruction SQL suivante:
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
La update
méthode vous oblige à unwrap
l' EntityManager
une mise en veille prolongée Session
.
Contrairement à merge
, l'entité détachée fournie va être réassociée au contexte de persistance actuel et une MISE À JOUR est planifiée pendant le vidage, que l'entité ait été modifiée ou non.
Pour éviter cela, vous pouvez utiliser l' @SelectBeforeUpdate
annotation Hibernate qui déclenchera une instruction SELECT qui récupère l'état chargé qui est ensuite utilisée par le mécanisme de vérification sale.
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
}
Méfiez-vous de l'exception NonUniqueObjectException
Un problème qui peut se produire update
est si le contexte de persistance contient déjà une référence d'entité avec le même identifiant 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 cas de test ci-dessus, Hibernate va lancer un NonUniqueObjectException
car le second EntityManager
contient déjà une Book
entité avec le même identifiant que celui auquel 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)
Conclusion
La merge
méthode est à privilégier si vous utilisez un verrouillage optimiste car elle vous permet d'éviter la perte de mises à jour. Pour plus de détails sur ce sujet, consultez cet article .
Il update
convient aux mises à jour par lots, car il peut empêcher l'instruction SELECT supplémentaire générée par l' merge
opération, réduisant ainsi le temps d'exécution de la mise à jour par lots.
refresh()
les entités détachées? En regardant à travers la spécification 2.0, je ne vois aucune justification; juste que ce n'est pas autorisé.