Come ho spiegato in questo articolo , dovresti preferire i metodi JPA il più delle volte e le updateattività di elaborazione batch.
Un'entità JPA o Hibernate può trovarsi in uno dei seguenti quattro stati:
- Transitorio (nuovo)
- Gestito (persistente)
- Distaccato
- Rimosso (eliminato)
La transizione da uno stato all'altro avviene tramite i metodi EntityManager o Session.
Ad esempio, l'APP EntityManagerfornisce i seguenti metodi di transizione dello stato dell'entità.

Hibernate Sessionimplementa tutti i EntityManagermetodi JPA e fornisce alcuni metodi di transizione di stato entità aggiuntivi come save, saveOrUpdatee update.

Persistere
Per cambiare lo stato di un'entità da Transitorio (Nuovo) a Gestito (Persistito), possiamo usare il persistmetodo offerto dall'APP EntityManagerche è anche ereditato da Hibernate Session.
Il persistmetodo attiva un PersistEventche è gestito dal DefaultPersistEventListenerlistener di eventi Hibernate.
Pertanto, quando si esegue il seguente test case:
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 genera le seguenti istruzioni SQL:
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
)
Si noti che idviene assegnato prima di associare l' Bookentità al contesto di persistenza corrente. Ciò è necessario perché le entità gestite sono archiviate in una Mapstruttura in cui la chiave è formata dal tipo di entità e dal suo identificatore e il valore è il riferimento dell'entità. Questo è il motivo per cui l'APP EntityManagere l'ibernazione Sessionsono noti come cache di primo livello.
Quando chiama persist, l'entità è collegata solo al contesto di persistenza attualmente in esecuzione e INSERT può essere posticipato fino a quando non flushviene chiamato.
L'unica eccezione è il generatore di IDENTITÀ che attiva immediatamente INSERT poiché è l'unico modo per ottenere l'identificatore di entità. Per questo motivo, Hibernate non può inserire in batch inserimenti per entità che utilizzano il generatore IDENTITY. Per maggiori dettagli su questo argomento, consulta questo articolo .
Salva
Il savemetodo specifico di Hibernate precede JPA ed è stato disponibile sin dall'inizio del progetto Hibernate.
Il savemetodo attiva un SaveOrUpdateEventche è gestito dal DefaultSaveOrUpdateEventListenerlistener di eventi Hibernate. Pertanto, il savemetodo è equivalente ai metodi updatee saveOrUpdate.
Per vedere come funziona il savemetodo, considerare il seguente caso di test:
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
);
});
Quando si esegue il test case sopra, Hibernate genera le seguenti istruzioni SQL:
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
)
Come puoi vedere, il risultato è identico alla persistchiamata del metodo. Tuttavia, a differenza persistdel savemetodo, restituisce l'identificatore di entità.
Per maggiori dettagli, consulta questo articolo .
Aggiornare
Il updatemetodo specifico di Hibernate ha lo scopo di bypassare il meccanismo di controllo sporco e forzare l'aggiornamento di un'entità al momento del flush.
Il updatemetodo attiva un SaveOrUpdateEventche è gestito dal DefaultSaveOrUpdateEventListenerlistener di eventi Hibernate. Pertanto, il updatemetodo è equivalente ai metodi savee saveOrUpdate.
Per vedere come funziona il updatemetodo, prendere in considerazione il seguente esempio che persiste Bookun'entità in una transazione, quindi la modifica mentre l'entità è nello stato separato e forza SQL UPDATE usando la updatechiamata del metodo.
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");
});
Quando si esegue il test case sopra, Hibernate genera le seguenti istruzioni SQL:
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
Si noti che UPDATEviene eseguito durante il flush del Persistence Context, subito prima del commit ed è per questo che il Updating the Book entitymessaggio viene registrato per primo.
Utilizzo @SelectBeforeUpdateper evitare aggiornamenti non necessari
Ora, l'AGGIORNAMENTO verrà sempre eseguito anche se l'entità non è stata modificata mentre si trova nello stato distaccato. Per evitare ciò, è possibile utilizzare l' @SelectBeforeUpdateannotazione Hibernate che attiverà SELECTun'istruzione recuperata loaded stateche verrà quindi utilizzata dal meccanismo di controllo sporco.
Quindi, se annotiamo l' Bookentità con l' @SelectBeforeUpdateannotazione:
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
Ed esegui il seguente test case:
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 esegue le seguenti istruzioni SQL:
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
Si noti che, questa volta, non viene UPDATEeseguito poiché il meccanismo di controllo sporco Hibernate ha rilevato che l'entità non è stata modificata.
SaveOrUpdate
Il saveOrUpdatemetodo specifico di Hibernate è solo un alias per savee update.
Il saveOrUpdatemetodo attiva un SaveOrUpdateEventche è gestito dal DefaultSaveOrUpdateEventListenerlistener di eventi Hibernate. Pertanto, il updatemetodo è equivalente ai metodi savee saveOrUpdate.
Ora puoi usare saveOrUpdatequando vuoi persistere un'entità o forzare una UPDATEcome illustrato dal seguente esempio.
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);
});
Attenzione al NonUniqueObjectException
Un problema che può verificarsi con save, updateed saveOrUpdateè se il contesto di persistenza contiene già un riferimento di entità con lo stesso ID e dello stesso tipo dell'esempio seguente:
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
);
}
Ora, quando si esegue il test case sopra, Hibernate lancia un NonUniqueObjectException perché il secondo EntityManagercontiene già Bookun'entità con lo stesso identificativo di quello a cui passiamo updatee il contesto di persistenza non può contenere due rappresentazioni della stessa 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)
Merge
Per evitare il NonUniqueObjectException , è necessario utilizzare il mergemetodo offerto dall'APP EntityManagere ereditato anche da Hibernate Session.
Come spiegato in questo articolo , mergerecupera una nuova istantanea dell'entità dal database se non è stato trovato alcun riferimento all'entità nel contesto di persistenza e copia lo stato dell'entità staccata passato al mergemetodo.
Il mergemetodo attiva un MergeEventche è gestito dal DefaultMergeEventListenerlistener di eventi Hibernate.
Per vedere come funziona il mergemetodo, considera il seguente esempio che persiste aBook un'entità in una transazione, quindi la modifica mentre l'entità è nello stato separato e passare l'entità staccata mergein un contesto di persistenza di sottosequenza.
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);
});
Durante l'esecuzione del test case sopra, Hibernate ha eseguito le seguenti istruzioni SQL:
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
Si noti che il riferimento all'entità restituito da merge è diverso da quello staccato che abbiamo passato al mergemetodo.
Ora, anche se dovresti preferire l'utilizzo di JPA merge durante la copia dello stato di entità distaccata, il costo aggiuntivo SELECTpuò essere problematico durante l'esecuzione di un'attività di elaborazione batch.
Per questo motivo, dovresti preferire l'utilizzo updatequando sei sicuro che non ci siano riferimenti a entità già associati al contesto di persistenza attualmente in esecuzione e che l'entità staccata è stata modificata.
Per maggiori dettagli su questo argomento, consulta questo articolo .
Conclusione
Per mantenere un'entità, è necessario utilizzare il persistmetodo JPA . Per copiare lo stato di entità staccata, mergedovrebbe essere preferito. Il updatemetodo è utile solo per le attività di elaborazione batch. Gli savee saveOrUpdatesono solo degli aliasupdate e probabilmente non dovresti usarli affatto.
Alcuni sviluppatori chiamano save anche quando l'entità è già gestita, ma questo è un errore e attiva un evento ridondante poiché, per le entità gestite, l'AGGIORNAMENTO viene gestito automaticamente al momento dello scaricamento del contesto di persistenza.
Per maggiori dettagli, consulta questo articolo .