Poiché questa è una domanda molto comune, ho scritto
questo articolo , su cui si basa questa risposta.
Stati delle entità
L'APP definisce i seguenti stati di entità:
Nuovo (Transitorio)
Un oggetto appena creato che non è mai stato associato a un ibernazione Session
(aka Persistence Context
) e non è mappato su nessuna riga della tabella del database è considerato nello stato Nuovo (Transitorio).
Per perseverare, è necessario chiamare esplicitamente il EntityManager#persist
metodo o utilizzare il meccanismo di persistenza transitiva.
Persistente (gestito)
Un'entità persistente è stata associata a una riga della tabella del database ed è gestita dal contesto di persistenza attualmente in esecuzione. Qualsiasi modifica apportata a tale entità verrà rilevata e propagata al database (durante il flush-time della sessione).
Con Hibernate, non è più necessario eseguire le istruzioni INSERT / UPDATE / DELETE. Hibernate utilizza uno stile di lavoro transattivo write-behind e le modifiche sono sincronizzate all'ultimo momento responsabile, durante l'attuale Session
flush-time.
Distaccato
Una volta chiuso il contesto di persistenza attualmente in esecuzione, tutte le entità gestite in precedenza vengono staccate. Le modifiche successive non verranno più monitorate e non verrà eseguita alcuna sincronizzazione automatica del database.
Transizioni dello stato delle entità
È possibile modificare lo stato dell'entità utilizzando vari metodi definiti EntityManager
dall'interfaccia.
Per comprendere meglio le transizioni di stato dell'entità JPA, considerare il diagramma seguente:
Quando si utilizza JPA, per riassociare un'entità staccata a un'attiva EntityManager
, è possibile utilizzare l' operazione di unione .
Quando si utilizza l'API Hibernate nativa, a parte merge
, è possibile ricollegare un'entità staccata a una Sessione di ibernazione attiva utilizzando i metodi di aggiornamento, come dimostrato dal diagramma seguente:
Unione di un'entità distaccata
L'unione sta per copiare lo stato di entità staccata (origine) in un'istanza di entità gestita (destinazione).
Considera che abbiamo persistito la seguente Book
entità, e ora l'entità è staccata come EntityManager
quella usata per persistere l'entità è stata chiusa:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
Mentre l'entità è nello stato distaccato, la modifichiamo come segue:
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
Ora, vogliamo propagare le modifiche al database, in modo da poter chiamare il merge
metodo:
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
E Hibernate eseguirà le seguenti istruzioni SQL:
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
Se l'entità risultante dalla fusione non ha equivalenti nella corrente EntityManager
, verrà recuperata una nuova istantanea dell'entità dal database.
Una volta che esiste un'entità gestita, JPA copia lo stato dell'entità staccata su quella attualmente gestita e durante il contesto di persistenzaflush
, verrà generato un AGGIORNAMENTO se il meccanismo di controllo sporco rileva che l'entità gestita è cambiata.
Pertanto, quando si utilizza merge
, l'istanza dell'oggetto disconnesso continuerà a rimanere staccata anche dopo l'operazione di unione.
Ricollegare un'entità staccata
Ibernazione, ma non JPA supporta il ricollegamento tramite il update
metodo.
Un Hibernate Session
può associare un solo oggetto entità per una determinata riga del database. Questo perché il contesto di persistenza funge da cache in memoria (cache di primo livello) e solo un valore (entità) è associato a una determinata chiave (tipo di entità e identificativo del database).
Un'entità può essere ricollegata solo se non esiste nessun altro oggetto JVM (corrispondente alla stessa riga del database) già associato all'ibernazione corrente Session
.
Considerando che abbiamo persistito l' Book
entità e che l'abbiamo modificata quando l' Book
entità era nello stato distaccato:
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"
);
Possiamo ricollegare l'entità staccata in questo modo:
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
LOGGER.info("Updating the Book entity");
});
E Hibernate eseguirà la seguente istruzione SQL:
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
Il update
metodo richiede per unwrap
la EntityManager
a un Hibernate Session
.
Diversamente merge
, l'entità staccata fornita verrà riassociata al contesto di persistenza corrente e durante l'aggiornamento viene pianificato un AGGIORNAMENTO indipendentemente dal fatto che l'entità abbia modificato o meno.
Per evitare ciò, è possibile utilizzare l' @SelectBeforeUpdate
annotazione Hibernate che attiverà un'istruzione SELECT che ha recuperato lo stato caricato che viene quindi utilizzato dal meccanismo di controllo sporco.
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
Fai attenzione a NonUniqueObjectException
Un problema che può verificarsi update
è 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 lancerà 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)
Conclusione
Il merge
metodo è preferibile se si utilizza il blocco ottimistico in quanto consente di evitare aggiornamenti persi. Per maggiori dettagli su questo argomento, consulta questo articolo .
Il update
è buono per aggiornamenti batch quanto esso può evitare SELECT aggiuntivo generato dal merge
funzionamento, riducendo quindi il tempo di aggiornamento di esecuzione batch.
refresh()
alle entità distaccate? Guardando attraverso le specifiche 2.0 non vedo alcuna giustificazione; solo che non è permesso.