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#persistmetodo 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 Sessionflush-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 EntityManagerdall'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 Bookentità, e ora l'entità è staccata come EntityManagerquella 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 mergemetodo:
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 updatemetodo.
Un Hibernate Sessionpuò 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' Bookentità e che l'abbiamo modificata quando l' Bookentità 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 updatemetodo richiede per unwrapla EntityManagera 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' @SelectBeforeUpdateannotazione 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 NonUniqueObjectExceptionperché 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)
Conclusione
Il mergemetodo è 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 mergefunzionamento, 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.