Da dies eine sehr häufige Frage ist, habe ich
diesen Artikel geschrieben , auf dem diese Antwort basiert.
Entitätszustände
JPA definiert die folgenden Entitätszustände:
Neu (vorübergehend)
Ein neu erstelltes Objekt, das noch nie einem Ruhezustand Session
(aka Persistence Context
) zugeordnet wurde und keiner Datenbanktabellenzeile zugeordnet ist, befindet sich im Status Neu (vorübergehend).
Um persistent zu werden, müssen wir entweder die EntityManager#persist
Methode explizit aufrufen oder den transitiven Persistenzmechanismus verwenden.
Dauerhaft (verwaltet)
Eine persistente Entität wurde einer Datenbanktabellenzeile zugeordnet und wird vom aktuell ausgeführten Persistenzkontext verwaltet. Jede an einer solchen Entität vorgenommene Änderung wird erkannt und an die Datenbank weitergegeben (während der Sitzungsspülzeit).
Mit Hibernate müssen wir keine INSERT / UPDATE / DELETE-Anweisungen mehr ausführen. Hibernate verwendet einen Transaktions-Write-Behind- Arbeitsstil und Änderungen werden im allerletzten verantwortlichen Moment während des Stroms synchronisiertSession
Spülzeit .
Freistehend
Sobald der aktuell ausgeführte Persistenzkontext geschlossen ist, werden alle zuvor verwalteten Entitäten getrennt. Aufeinanderfolgende Änderungen werden nicht mehr verfolgt und es findet keine automatische Datenbanksynchronisierung statt.
Übergänge des Entitätsstatus
Sie können den Entitätsstatus mit verschiedenen Methoden ändern, die durch das definiert sind EntityManager
Schnittstelle .
Betrachten Sie das folgende Diagramm, um die Übergänge des JPA-Entitätsstatus besser zu verstehen:
Wenn Sie JPA verwenden, um eine getrennte Entität einer aktiven zuzuordnen EntityManager
, können Sie die Zusammenführung verwenden verwenden.
Wenn Sie die native Hibernate-API verwenden, merge
können Sie außerdem eine getrennte Entität mithilfe der Aktualisierungsmethoden erneut einer aktiven Hibernate-Sitzung zuordnen, wie im folgenden Diagramm dargestellt:
Zusammenführen einer getrennten Einheit
Durch die Zusammenführung wird der Status der getrennten Entität (Quelle) in eine Instanz einer verwalteten Entität (Ziel) kopiert.
Angenommen, wir haben die folgende Book
Entität beibehalten, und jetzt wird die Entität getrennt, da die EntityManager
zum Bestehen der Entität verwendete Entität geschlossen wurde:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
Während sich die Entität im getrennten Zustand befindet, ändern wir sie wie folgt:
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
Jetzt möchten wir die Änderungen an die Datenbank weitergeben, damit wir die merge
Methode aufrufen können :
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
Und Hibernate wird die folgenden SQL-Anweisungen ausführen:
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
Wenn die fusionierende Entität im aktuellen keine Entsprechung hat EntityManager
, wird ein neuer Entitätsschnappschuss aus der Datenbank abgerufen.
Sobald eine verwaltete Entität vorhanden ist, kopiert JPA den Status der getrennten Entität auf die aktuell verwaltete Entität. Während des Persistenzkontextsflush
wird ein UPDATE generiert, wenn der Dirty-Checking-Mechanismus feststellt, dass sich die verwaltete Entität geändert hat.
Bei Verwendung merge
bleibt die Instanz des getrennten Objekts auch nach dem Zusammenführungsvorgang weiterhin getrennt.
Erneutes Anhängen einer getrennten Entität
Ruhezustand, aber nicht JPA unterstützt das erneute Anhängen über die update
Methode.
Ein Ruhezustand Session
kann nur ein Entitätsobjekt für eine bestimmte Datenbankzeile zuordnen. Dies liegt daran, dass der Persistenzkontext als speicherinterner Cache (Cache der ersten Ebene) fungiert und einem bestimmten Schlüssel (Entitätstyp und Datenbankkennung) nur ein Wert (Entität) zugeordnet ist.
Eine Entität kann nur dann erneut zugeordnet werden, wenn dem aktuellen Ruhezustand noch kein anderes JVM-Objekt (das mit derselben Datenbankzeile übereinstimmt) bereits zugeordnet ist Session
.
In Anbetracht dessen Book
, dass wir die Book
Entität beibehalten haben und dass wir sie geändert haben, als sich die Entität im getrennten Zustand befand:
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"
);
Wir können die abgetrennte Entität wie folgt wieder anbringen:
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
LOGGER.info("Updating the Book entity");
});
Und Hibernate führt die folgende SQL-Anweisung aus:
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
Die update
Methode erfordert, dass Sie in unwrap
den EntityManager
Ruhezustand wechseln Session
.
Im Gegensatz dazu merge
wird die bereitgestellte getrennte Entität dem aktuellen Persistenzkontext neu zugeordnet, und während des Flushs wird ein UPDATE geplant, unabhängig davon, ob die Entität geändert wurde oder nicht.
Um dies zu verhindern, können Sie die @SelectBeforeUpdate
Annotation Hibernate verwenden, die eine SELECT-Anweisung auslöst, die den geladenen Status abruft, der dann vom Dirty-Checking-Mechanismus verwendet wird.
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
Achten Sie auf die NonUniqueObjectException
Ein Problem, das auftreten kann, update
besteht darin, dass der Persistenzkontext bereits eine Entitätsreferenz mit derselben ID und demselben Typ wie im folgenden Beispiel enthält:
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
);
}
Wenn Sie den obigen Testfall ausführen, wird Hibernate a auslösen, NonUniqueObjectException
da der zweite EntityManager
bereits eine Book
Entität mit derselben Kennung enthält wie die, an die wir übergeben update
, und der Persistenzkontext nicht zwei Darstellungen derselben Entität enthalten kann.
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)
Fazit
Die merge
Methode ist vorzuziehen, wenn Sie optimistische Sperren verwenden, um verlorene Aktualisierungen zu vermeiden. Weitere Informationen zu diesem Thema finden Sie in diesem Artikel .
Dies update
ist gut für Stapelaktualisierungen, da es die zusätzliche SELECT-Anweisung verhindern kann, die durch die merge
Operation generiert wird , wodurch die Ausführungszeit für Stapelaktualisierungen verkürzt wird.
refresh()
getrennte Entitäten nicht zulässt . Wenn ich mir die 2.0-Spezifikation ansehe, sehe ich keine Rechtfertigung. nur dass es nicht erlaubt ist.