JPA und Hibernate bieten verschiedene Methoden, um neue Entitäten beizubehalten und vorhandene zu aktualisieren. Sie können zwischen den Methoden persist und Merge von JPA und save und update von Hibernate wählen.
Es scheint, als gäbe es 2 Paare von 2 Methoden, die dasselbe tun. Sie können die Methoden persist und save verwenden, um eine neue Entität zu speichern, und die Methoden merge und update, um die Änderungen einer getrennten Entität in der Datenbank zu speichern. Deshalb fragen sich viele Entwickler, welche dieser Methoden sie verwenden sollten. Schauen wir uns die Details und kleinen Unterschiede dieser Methoden genauer an.
Besonderer Dank geht an Steve Ebersole (Lead Developer – Hibernate ORM), der sein Feedback und seine großartigen Einblicke in einige der versteckten Implementierungsdetails von Hibernate gegeben hat!
- Entity State Transitions
- Persistieren einer neuen Entität Mit persist Oder save
- Spezifikation vs. Proprietäre API
- Rückgabetypen und Ausführung von SQL-Anweisungen
- Nicht generiert
- Generiert mit Identitätsstrategie
- Generiert mit der Sequenzstrategie
- Generiert mit der Tabellenstrategie
- Welche zu wählen?
- Aktualisieren einer getrennten Entität
- Zusammenführungsmethode von JPA
- Aktualisierungsmethode von Hibernate
- Welche zu wählen?
- Aktualisieren einer verwalteten Entität
Entity State Transitions
Bevor wir auf die Details dieser 4 Methoden eingehen, muss ich Ihnen eine kurze Einführung in die Entity Lifecycle States von JPA geben.
Wenn eine Entität an den aktuellen Persistenzkontext angehängt ist, wird der Lebenszyklusstatus verwaltet. Das bedeutet, dass es einem Datenbankeintrag zugeordnet ist. Ihr Persistenzanbieter generiert die erforderlichen SQL INSERT- und UPDATE-Anweisungen, um alle Änderungen weiterzugeben. Eine verwaltete Entität wird auch im Cache der 1. Ebene gespeichert.
Wenn Sie eine neue Entität erstellen, befindet sie sich im transienten Zustand. Es bleibt in diesem Zustand, bis Sie es an den aktuellen Persistenzkontext anhängen. Im folgenden Abschnitt zeige ich Ihnen, wie Sie dies mit der Persist- und der Save-Methode von JPA im Ruhezustand tun können. Solange sich eine Entität im transienten Zustand befindet, wird sie keinem Datenbankeintrag zugeordnet und von keinem Persistenzkontext verwaltet.
Entitäten im getrennten Lebenszyklusstatus werden nicht mehr vom Persistenzkontext verwaltet. Dies kann der Fall sein, weil Sie den Persistenzkontext geschlossen oder die Entität explizit vom aktuellen Kontext getrennt haben. In einem späteren Teil dieses Beitrags werde ich näher darauf eingehen, wie Sie diese Entitäten mit den Methoden Merge und Hibernate von JPA erneut verknüpfen können.
Und der letzte Lebenszyklusstatus wird entfernt. Diese Entitäten befanden sich zuvor im Status verwaltet, bevor Sie sie zum Entfernen geplant haben. Das Entfernen von Entitäten liegt außerhalb des Geltungsbereichs dieses Beitrags, daher werde ich nicht zu viele Details dazu erläutern. Sie können eine Entität zum Entfernen planen, indem Sie die Methode remove auf der EntityManager-Schnittstelle aufrufen.
Persistieren einer neuen Entität Mit persist Oder save
Wenn Sie ein neues Entitätsobjekt erstellen, befindet es sich im transienten Lebenszyklusstatus. Es ordnet keinen Datenbankeintrag zu.
Author a = new Author();a.setFirstName("Thorben");a.setLastName("Janssen");
Sie müssen die Entität an einen Persistenzkontext anhängen, damit sie verwaltet und in der Datenbank beibehalten wird. Sie können dazu entweder die Persist-Methode von JPA oder die Save-Methode von Hibernate verwenden. Beide Methoden scheinen dasselbe zu tun, es gibt jedoch einige Unterschiede.
Spezifikation vs. Proprietäre API
Der offensichtlichste Unterschied besteht darin, dass die JPA-Spezifikation die Persist-Methode definiert. Sie können es mit allen JPA-Implementierungen verwenden. Die Speichermethode hingegen ist Hibernate-spezifisch. Es ist daher in anderen JPA-Implementierungen nicht verfügbar.
Dies ist jedoch nur relevant, wenn Sie den Ruhezustand durch eine andere JPA-Implementierung wie Eclipse Link oder OpenJPA ersetzen möchten.
Rückgabetypen und Ausführung von SQL-Anweisungen
Ein weiterer offensichtlicher Unterschied zwischen diesen 2 Methoden ist ihr Rückgabetyp. Die persist-Methode von JPA gibt void zurück und die save-Methode von Hibernate gibt den Primärschlüssel der Entität zurück.
Das mag ein großer Unterschied sein, besonders wenn man sich das Javadoc von Hibernate und die JPA-Spezifikation genauer ansieht:
- Das Javadoc der Speichermethode von Hibernate gibt an, dass zuerst der Primärschlüsselwert generiert wird:
Behalten Sie die angegebene transiente Instanz bei, indem Sie zuerst einen generierten Bezeichner zuweisen.
Javadoc-Sitzung.speichern (Entität) - Sie finden keine Informationen dazu in der JPA-Spezifikation. Es wird nicht definiert, wann der Primärschlüsselwert zugewiesen werden muss. Der Persistenzanbieter kann dies also jederzeit zwischen dem Aufruf der persist-Methode und dem Löschen des Persistenzkontexts tun.
In den meisten Fällen macht es keinen Unterschied, ob Sie die Methode save oder persist aufrufen. Hibernate verwendet den Namen der Entitätsklasse und den Primärschlüsselwert, um die Entität im Cache der ersten Ebene zu speichern. Es benötigt daher einen Primärschlüsselwert, wenn es die persist-Methode ausführt.
In fast allen Situationen generiert Hibernate den Primärschlüsselwert sofort und löst bei Bedarf eine SQL-Anweisung aus, wenn Sie die Methode persist oder save aufrufen.
Dies ist jedoch nicht der Fall, wenn Sie die Identitätsstrategie verwenden und versuchen, eine Entität ohne aktive Transaktion oder mit FlushMode beizubehalten.MANUAL. Wenn Sie die persist-Methode in einer dieser Situationen aufrufen, verzögert Hibernate die Ausführung der SQL INSERT-Anweisung und erstellt einen temporären Primärschlüsselwert. Wenn Sie jedoch die save-Methode aufrufen, führt Hibernate die SQL INSERT-Anweisung sofort aus und ruft den Primärschlüsselwert aus der Datenbank ab.
Sie können es dann als Rückgabewert der save-Methode abrufen.
Author a = new Author();a.setFirstName("Thorben");a.setLastName("Janssen");Long id = (Long) em.unwrap(Session.class).save(a);
Oder Sie können die Getter-Methode des Primärschlüsselattributs Ihrer verwalteten Entität aufrufen, wenn Sie die persist-Methode von JPA verwenden.
Author a = new Author();a.setFirstName("Torben");a.setLastName("Janssen");em.persist(a);Long id = a.getId();
Hibernate führt dieselben SQL-Anweisungen aus, wenn Sie die Methode persist oder save aufrufen. Was und wann dies geschieht, hängt von Ihrer Strategie zur Primärschlüsselgenerierung ab:
Nicht generiert
Wenn Sie den Primärschlüsselwert programmgesteuert festlegen, z. B. auf einen natürlichen Bezeichner, führt Hibernate eine SQL INSERT-Anweisung nur aus, wenn der Persistenzkontext geleert wird.
14:08:34,979 INFO TestPersistSaveMerge:237 - Save entity14:08:35,052 INFO TestPersistSaveMerge:240 - Commit transaction14:08:35,123 DEBUG SQL:92 - insert into Author (firstName, lastName, version, id) values (?, ?, ?, ?)
Generiert mit Identitätsstrategie
Wenn Sie die Identitätsstrategie verwenden, um den Primärschlüsselwert zu generieren, muss Hibernate die INSERT-Anweisung ausführen, wenn Sie die save- oder persist-Methode aufrufen, um den Primärschlüsselwert aus der Datenbank abzurufen.
14:09:28,264 INFO TestPersistSaveMerge:237 - Save entity14:09:28,336 DEBUG SQL:92 - insert into Author (firstName, lastName, version) values (?, ?, ?)14:09:28,354 INFO TestPersistSaveMerge:240 - Commit transaction
Generiert mit der Sequenzstrategie
Und wenn Sie die SEQUENZ verwenden, führt Hibernate eine SQL SELECT-Anweisung aus, um den nächsten Wert aus der Datenbanksequenz abzurufen. Hibernate verzögert dann die INSERT-Anweisung, bis der Persistenzkontext geleert wird. In diesem Beispiel erfolgt der Flush, wenn die Transaktion festgeschrieben wird.
14:10:27,994 INFO TestPersistSaveMerge:237 - Save entity14:10:28,002 DEBUG SQL:92 - select nextval ('hibernate_sequence')14:10:28,042 INFO TestPersistSaveMerge:240 - Commit transaction14:10:28,096 DEBUG SQL:92 - insert into Author (firstName, lastName, version, id) values (?, ?, ?, ?)
Generiert mit der Tabellenstrategie
Sie sollten die Tabellenstrategie nicht verwenden, da sie Sperren auf Zeilenebene für die Primärschlüsseltabelle erfordert und nicht gut skaliert werden kann. Wenn Sie diese Strategie trotzdem verwenden, führt Hibernate eine SQL SELECT-Anweisung aus, um den nächsten Primärschlüsselwert aus der Datenbank abzurufen, und schreibt den neuen Wert in die Datenbanktabelle. Es verzögert die Ausführung der SQL INSERT-Anweisung für die neue Entität, bis der Persistenzkontext geleert wird.
14:11:17,368 INFO TestPersistSaveMerge:237 - Save entity14:11:17,482 DEBUG SQL:92 - select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update of tbl14:11:17,531 DEBUG SQL:92 - insert into hibernate_sequences (sequence_name, next_val) values (?,?)14:11:17,534 DEBUG SQL:92 - update hibernate_sequences set next_val=? where next_val=? and sequence_name=?14:11:17,584 INFO TestPersistSaveMerge:240 - Commit transaction14:11:17,655 DEBUG SQL:92 - insert into Author (firstName, lastName, version, id) values (?, ?, ?, ?)
Welche zu wählen?
Sie könnten erwarten, dass sich die Methoden save und persist unterschiedlich verhalten, da es einige Unterschiede zwischen der JPA-Spezifikation und dem Javadoc der proprietären Methoden von Hibernate gibt.
Aber fast alle diese Unterschiede verschwinden, wenn man sich die interne Implementierung ansieht. Die einzigen, die übrig bleiben, sind 2 Eckfälle, in denen der Ruhezustand den Abruf des Primärschlüssels, den Rückgabetyp der Methode und die Unterstützung durch andere JPA-Implementierungen verzögern kann.
Für die meisten Anwendungen macht es keinen Unterschied, ob Sie den generierten Primärschlüsselwert als Rückgabetyp der Speichermethode von Hibernate oder von der Getter-Methode Ihres Primärschlüsselattributs erhalten. Solange Sie keinen erweiterten Persistenzkontext verwenden und alle Datenbankoperationen mit einer aktiven Transaktion ausführen, empfehle ich die persist-Methode von JPA.
Aktualisieren einer getrennten Entität
Wenn Sie den aktuellen Persistenzkontext schließen oder eine Entität explizit daraus entfernen, indem Sie die Methoden clear oder detach auf der EntityManager-Schnittstelle aufrufen, wird die Entität getrennt. Dies bedeutet, dass es nicht mehr im Cache der 1. Ebene gespeichert wird und dass der Ruhezustand keine der angewendeten Änderungen an der Datenbank repliziert.
Sie können die Update- oder die Merge-Methode von Hibernate verwenden, um eine getrennte Entität einem Persistenzkontext zuzuordnen. Nachdem Sie dies getan haben, aktualisiert Hibernate die Datenbank basierend auf den Entitätsattributwerten.
Die Wirkung der Update- und Merge-Methode scheint die gleiche zu sein, aber wie Sie in den folgenden Abschnitten sehen werden, gibt es einen wichtigen Unterschied.
Zusammenführungsmethode von JPA
Die Zusammenführungsmethode von JPA kopiert den Status einer getrennten Entität in eine verwaltete Instanz derselben Entität. Hibernate führt daher eine SQL SELECT-Anweisung aus, um eine verwaltete Entität aus der Datenbank abzurufen. Wenn der Persistenzkontext bereits eine verwaltete Instanz der Entität enthielt, verwendet Hibernate stattdessen die vorhandene. Anschließend werden alle Attributwerte in die verwaltete Entität kopiert und an den Aufrufer zurückgegeben.
Author managedAuthor = em.merge(a);
Nachdem Sie die Protokollierung von SQL-Anweisungen aktiviert haben, sehen Sie die ausgeführten SELECT- und UPDATE-Anweisungen in der Protokollausgabe.
11:37:21,172 DEBUG SQL:92 - select books0_.bookId as bookId1_2_0_, books0_.authorId as authorId2_2_0_, book1_.id as id1_1_1_, book1_.fk_author as fk_autho6_1_1_, book1_.format as format2_1_1_, book1_.publishingDate as publishi3_1_1_, book1_.title as title4_1_1_, book1_.version as version5_1_1_, author2_.id as id1_0_2_, author2_.firstName as firstNam2_0_2_, author2_.lastName as lastName3_0_2_, author2_.version as version4_0_2_ from BookAuthor books0_ inner join Book book1_ on books0_.authorId=book1_.id left outer join Author author2_ on book1_.fk_author=author2_.id where books0_.bookId=?11:37:21,180 INFO TestPersistSaveMerge:82 - Before commit11:37:21,182 DEBUG SQL:92 - update Author set firstName=?, lastName=?, version=? where id=? and version=?
Wenn Hibernate den Persistenzkontext für das nächste Mal leert, überprüft sein schmutziger Überprüfungsmechanismus alle verwalteten Entitäten. Wenn festgestellt wird, dass der Zusammenführungsvorgang einen Entitätsattributwert geändert hat, wird die erforderliche SQL UPDATE-Anweisung ausgelöst.
Es gibt ein wichtiges Detail, das Sie wissen müssen, wenn Sie die Merge-Methode von JPA verwenden. Hibernate kopiert die Attributwerte der getrennten Entität in die verwaltete Entität. Dadurch werden alle Änderungen überschrieben, die Sie in der aktuellen Sitzung an dieser Entität vorgenommen haben.
Aktualisierungsmethode von Hibernate
Die Aktualisierungsmethode von Hibernate löst keine SQL SELECT-Anweisung aus. Es hängt die Entität nur an den aktuellen Persistenzkontext an. Im Gegensatz zur Merge-Methode von JPA können Sie keine Änderungen verlieren, indem Sie die update-Methode aufrufen. Wenn der Persistenzkontext bereits eine verwaltete Instanz der Entität enthält, die Sie aktualisieren möchten, wird eine Ausnahme ausgelöst.
em.unwrap(Session.class).update(a);
Wenn der Ruhezustand den nächsten Flush ausführt, führt er keine schmutzigen Überprüfungen durch. Dies ist nicht möglich, da Hibernate nicht die neueste Version der Entität aus der Datenbank gelesen hat. Es führt nur eine SQL UPDATE-Anweisung für die wieder angefügte Entität aus.
11:38:28,151 INFO TestPersistSaveMerge:121 - Before commit11:38:28,153 DEBUG SQL:92 - update Author set firstName=?, lastName=?, version=? where id=? and version=?
Die fehlende Dirty-Prüfung führt zu einer unnötigen SQL UPDATE-Anweisung, wenn die Entität und der entsprechende Datenbankeintrag dieselben Werte enthalten. Dies kann ein Problem sein, wenn Ihr DBA einen Aktualisierungsauslöser für die Datenbanktabelle registriert hat. In diesen Situationen können Sie Ihre Entität mit @SelectBeforeUpdate kommentieren.
@Entity@SelectBeforeUpdatepublic class Author { ... }
Das Hibernate anweist, die Entität auszuwählen und eine schmutzige Prüfung durchzuführen, bevor die SQL UPDATE-Anweisung generiert wird. Wie Sie in der Protokollausgabe sehen können, ähnelt das Verhalten der Update-Methode jetzt der Merge-Methode von JPA.
19:08:16,530 INFO TestPersistSaveMerge:121 - Before commit19:08:16,531 DEBUG SQL:92 - select author_.id, author_.firstName as firstNam2_0_, author_.lastName as lastName3_0_, author_.version as version4_0_ from Author author_ where author_.id=?19:08:16,592 DEBUG SQL:92 - update Author set firstName=?, lastName=?, version=? where id=? and version=?
Es gibt jedoch einen signifikanten Unterschied zwischen den 2 Methoden. Wenn Sie die update-Methode aufrufen, wählt Hibernate nur die Entität aus, die Sie als Methodenparameter angegeben haben. Wenn Sie jedoch die Merge-Methode von JPA aufrufen, wählt Hibernate auch alle Zuordnungen mit CascadeType aus.ZUSAMMENFÜHREN. Sie sollten daher die Merge-Methode von JPA bevorzugen, wenn Sie ein großes Diagramm von Entitäten erneut anhängen.
Welche zu wählen?
Auf diese Frage gibt es keine generelle Antwort. Wie Sie gesehen haben, haben beide Methoden ihre Vor- und Nachteile. Sie müssen für Ihren spezifischen Anwendungsfall entscheiden, ob Hibernate die Entität auswählen muss, bevor sie die SQL UPDATE-Anweisung auslöst. In diesem Fall müssen Sie auch die Tiefe Ihres Entitätsdiagramms und die Auswirkungen des bereitgestellten Abrufverhaltens auf die Leistung berücksichtigen.
Aktualisieren einer verwalteten Entität
JPA und Hibernate machen es sehr einfach, eine verwaltete Entität zu aktualisieren. Wenn sich Ihre Entität im verwalteten Lebenszyklusstatus befindet, z. B. weil Sie sie mit einer JPQL-Abfrage oder der find-Methode des EntityManagers abgerufen haben, müssen Sie nur die Werte Ihrer Entitätsattribute ändern.
em = emf.createEntityManager();em.getTransaction().begin();a = em.find(Author.class, a.getId());a.setFirstName("Thorben");log.info("Before commit");em.getTransaction().commit();em.close();
Wenn Hibernate beschließt, den Persistenzkontext zu leeren, erkennt der Dirty Checking-Mechanismus die Änderung und führt die erforderliche SQL UPDATE-Anweisung aus.
11:41:49,178 DEBUG SQL:92 - select author0_.id as id1_0_0_, author0_.firstName as firstNam2_0_0_, author0_.lastName as lastName3_0_0_, author0_.version as version4_0_0_ from Author author0_ where author0_.id=?11:41:49,191 INFO TestPersistSaveMerge:335 - Before commit11:41:49,193 DEBUG SQL:92 - update Author set firstName=?, lastName=?, version=? where id=? and version=?
Sie müssen und sollten die Speichermethode von Hibernate nicht aufrufen, nachdem Sie eine Entität aktualisiert haben. Dies löst ein zusätzliches saveOrUpdate-Ereignis aus, ohne Vorteile zu bieten. Wenn Hibernate beschließt, den Persistenzkontext zu leeren, führt es trotzdem die Dirty-Prüfung durch, um alle Änderungen zu erkennen, bevor es die erforderlichen SQL UPDATE-Anweisungen ausführt.