Comme je l'ai expliqué dans cet article , vous devriez préférer les méthodes JPA la plupart du temps et les update
tâ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 EntityManager
fournit les méthodes de transition d'état d'entité suivantes.
Hibernate Session
implémente toutes les EntityManager
méthodes JPA et fournit des méthodes de transition d'état d'entité supplémentaires comme save
, saveOrUpdate
et update
.
Persister
Pour changer l'état d'une entité de Transitoire (Nouveau) à Géré (Persistant), nous pouvons utiliser la persist
méthode proposée par le JPA EntityManager
qui est également héritée par l'Hibernate Session
.
La persist
méthode déclenche un PersistEvent
qui 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 id
est attribué avant d'attacher l' Book
entité au contexte de persistance actuel. Cela est nécessaire car les entités gérées sont stockées dans une Map
structure 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 EntityManager
et le Hibernate Session
sont 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' flush
appel 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 save
méthode spécifique à Hibernate est antérieure à JPA et elle est disponible depuis le début du projet Hibernate.
La save
méthode déclenche un SaveOrUpdateEvent
qui est géré par l' DefaultSaveOrUpdateEventListener
écouteur d'événements Hibernate. Par conséquent, la save
méthode est équivalente aux méthodes update
et saveOrUpdate
.
Pour voir comment fonctionne la save
mé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' persist
appel de méthode. Cependant, contrairement à persist
, la save
méthode renvoie l'identifiant d'entité.
Pour plus de détails, consultez cet article .
Mettre à jour
La update
mé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 update
méthode déclenche un SaveOrUpdateEvent
qui est géré par l' DefaultSaveOrUpdateEventListener
écouteur d'événements Hibernate. Par conséquent, la update
méthode est équivalente aux méthodes save
et saveOrUpdate
.
Pour voir comment fonctionne la update
méthode, considérez l'exemple suivant qui persiste une Book
entité 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' update
appel 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 UPDATE
est exécuté pendant le vidage du contexte de persistance, juste avant la validation, et c'est pourquoi le Updating the Book entity
message est enregistré en premier.
Utilisation @SelectBeforeUpdate
pour é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' @SelectBeforeUpdate
annotation Hibernate qui déclenchera une SELECT
instruction récupérée loaded state
qui sera ensuite utilisée par le mécanisme de vérification sale.
Donc, si nous annotons l' Book
entité avec l' @SelectBeforeUpdate
annotation:
@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' UPDATE
exécutions puisque le mécanisme de vérification sale Hibernate a détecté que l'entité n'a pas été modifiée.
SaveOrUpdate
La saveOrUpdate
méthode spécifique à Hibernate n'est qu'un alias pour save
et update
.
La saveOrUpdate
méthode déclenche un SaveOrUpdateEvent
qui est géré par l' DefaultSaveOrUpdateEventListener
écouteur d'événements Hibernate. Par conséquent, la update
méthode est équivalente aux méthodes save
et saveOrUpdate
.
Maintenant, vous pouvez utiliser saveOrUpdate
lorsque vous souhaitez conserver une entité ou forcer une UPDATE
comme 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
, update
et saveOrUpdate
est 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 NonUniqueObjectException
car le second EntityManager
contient déjà une Book
entité 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 merge
méthode proposée par la JPA EntityManager
et héritée également par l'Hibernate Session
.
Comme expliqué dans cet article , le merge
ré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 merge
méthode.
La merge
méthode déclenche un MergeEvent
qui est géré par l' DefaultMergeEventListener
écouteur d'événements Hibernate.
Pour voir comment fonctionne la merge
méthode, considérez l'exemple suivant qui persiste une Book
entité dans une transaction, puis il la modifie pendant que l'entité est à l'état détaché et passe l'entité détachée à merge
dans 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 merge
est différente de celle détachée que nous avons passée à la merge
méthode.
Maintenant, bien que vous préfériez utiliser JPA merge
lors de la copie de l'état d'entité détachée, le supplément SELECT
peut ê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 update
lorsque 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 persist
méthode JPA . Pour copier l'état de l'entité détachée, merge
doit être préféré. La update
méthode est utile pour les tâches de traitement par lots uniquement. Les save
et ne saveOrUpdate
sont que des alias update
et vous ne devriez probablement pas les utiliser du tout.
Certains développeurs appellent save
mê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 .