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.

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.

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 .