Come ho spiegato in questo articolo , dovresti preferire i metodi JPA il più delle volte e le update
attività 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 EntityManager
fornisce i seguenti metodi di transizione dello stato dell'entità.
Hibernate Session
implementa tutti i EntityManager
metodi JPA e fornisce alcuni metodi di transizione di stato entità aggiuntivi come save
, saveOrUpdate
e update
.
Persistere
Per cambiare lo stato di un'entità da Transitorio (Nuovo) a Gestito (Persistito), possiamo usare il persist
metodo offerto dall'APP EntityManager
che è anche ereditato da Hibernate Session
.
Il persist
metodo attiva un PersistEvent
che è gestito dal DefaultPersistEventListener
listener 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 id
viene assegnato prima di associare l' Book
entità al contesto di persistenza corrente. Ciò è necessario perché le entità gestite sono archiviate in una Map
struttura 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 EntityManager
e l'ibernazione Session
sono 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 flush
viene 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 save
metodo specifico di Hibernate precede JPA ed è stato disponibile sin dall'inizio del progetto Hibernate.
Il save
metodo attiva un SaveOrUpdateEvent
che è gestito dal DefaultSaveOrUpdateEventListener
listener di eventi Hibernate. Pertanto, il save
metodo è equivalente ai metodi update
e saveOrUpdate
.
Per vedere come funziona il save
metodo, 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 persist
chiamata del metodo. Tuttavia, a differenza persist
del save
metodo, restituisce l'identificatore di entità.
Per maggiori dettagli, consulta questo articolo .
Aggiornare
Il update
metodo 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 update
metodo attiva un SaveOrUpdateEvent
che è gestito dal DefaultSaveOrUpdateEventListener
listener di eventi Hibernate. Pertanto, il update
metodo è equivalente ai metodi save
e saveOrUpdate
.
Per vedere come funziona il update
metodo, prendere in considerazione il seguente esempio che persiste Book
un'entità in una transazione, quindi la modifica mentre l'entità è nello stato separato e forza SQL UPDATE usando la update
chiamata 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 UPDATE
viene eseguito durante il flush del Persistence Context, subito prima del commit ed è per questo che il Updating the Book entity
messaggio viene registrato per primo.
Utilizzo @SelectBeforeUpdate
per 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' @SelectBeforeUpdate
annotazione Hibernate che attiverà SELECT
un'istruzione recuperata loaded state
che verrà quindi utilizzata dal meccanismo di controllo sporco.
Quindi, se annotiamo l' Book
entità con l' @SelectBeforeUpdate
annotazione:
@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 UPDATE
eseguito poiché il meccanismo di controllo sporco Hibernate ha rilevato che l'entità non è stata modificata.
SaveOrUpdate
Il saveOrUpdate
metodo specifico di Hibernate è solo un alias per save
e update
.
Il saveOrUpdate
metodo attiva un SaveOrUpdateEvent
che è gestito dal DefaultSaveOrUpdateEventListener
listener di eventi Hibernate. Pertanto, il update
metodo è equivalente ai metodi save
e saveOrUpdate
.
Ora puoi usare saveOrUpdate
quando vuoi persistere un'entità o forzare una UPDATE
come 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
, update
ed 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 EntityManager
contiene già Book
un'entità con lo stesso identificativo di quello a cui passiamo update
e 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 merge
metodo offerto dall'APP EntityManager
e ereditato anche da Hibernate Session
.
Come spiegato in questo articolo , merge
recupera 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 merge
metodo.
Il merge
metodo attiva un MergeEvent
che è gestito dal DefaultMergeEventListener
listener di eventi Hibernate.
Per vedere come funziona il merge
metodo, 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 merge
in 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 merge
metodo.
Ora, anche se dovresti preferire l'utilizzo di JPA merge
durante la copia dello stato di entità distaccata, il costo aggiuntivo SELECT
può essere problematico durante l'esecuzione di un'attività di elaborazione batch.
Per questo motivo, dovresti preferire l'utilizzo update
quando 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 persist
metodo JPA . Per copiare lo stato di entità staccata, merge
dovrebbe essere preferito. Il update
metodo è utile solo per le attività di elaborazione batch. Gli save
e saveOrUpdate
sono 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 .