Ruhezustand: Speichern, beibehalten, aktualisieren, zusammenführen, saveOrUpdate

1. Einleitung

In diesem Artikel werden wir die Unterschiede zwischen den verschiedenen Methoden der Diskussion Session - Schnittstelle: speichern , bestehen bleiben , Update , merge , saveOrUpdate .

Dies ist keine Einführung in den Ruhezustand, und Sie sollten bereits die Grundlagen der Konfiguration, der objektrelationalen Zuordnung und der Arbeit mit Entitätsinstanzen kennen. Ein Einführungsartikel zu Hibernate finden Sie in unserem Tutorial zu Hibernate 4 with Spring.

2. Sitzung als Persistenzkontextimplementierung

Die Sitzungsschnittstelle verfügt über mehrere Methoden, die letztendlich zum Speichern von Daten in der Datenbank führen: persistieren , speichern , aktualisieren , zusammenführen , saveOrUpdate . Um den Unterschied zwischen diesen Methoden zu verstehen, müssen wir zuerst den Zweck der Sitzung als Persistenzkontext und den Unterschied zwischen den Zuständen von Entitätsinstanzen in Bezug auf die Sitzung diskutieren .

Wir sollten auch die Geschichte der Hibernate-Entwicklung verstehen, die zu einigen teilweise duplizierten API-Methoden führte.

2.1. Entitätsinstanzen verwalten

Abgesehen von der objektrelationalen Zuordnung selbst ist eines der Probleme, die Hibernate lösen sollte, das Problem der Verwaltung von Entitäten zur Laufzeit. Der Begriff „Persistenzkontext“ ist die Lösung von Hibernate für dieses Problem. Der Persistenzkontext kann als Container oder Cache der ersten Ebene für alle Objekte betrachtet werden, die Sie während einer Sitzung geladen oder in einer Datenbank gespeichert haben.

Die Sitzung ist eine logische Transaktion, deren Grenzen durch die Geschäftslogik Ihrer Anwendung definiert werden. Wenn Sie mit der Datenbank über einen Persistenzkontext arbeiten und alle Ihre Entitätsinstanzen an diesen Kontext angehängt sind, sollten Sie für jeden Datenbankdatensatz, mit dem Sie während der Sitzung interagiert haben, immer eine einzige Entitätsinstanz haben.

Im Ruhezustand wird der Persistenzkontext durch die Instanz org.hibernate.Session dargestellt . Für JPA ist es der javax.persistence.EntityManager . Wenn wir Hibernate als JPA-Anbieter verwenden und über die EntityManager- Schnittstelle arbeiten, umschließt die Implementierung dieser Schnittstelle im Wesentlichen das zugrunde liegende Sitzungsobjekt . Hibernate Session bietet jedoch eine umfangreichere Benutzeroberfläche mit mehr Möglichkeiten, sodass es manchmal nützlich ist, direkt mit Session zu arbeiten .

2.2. Instanzen von Entitätszuständen

Jede Entitätsinstanz in Ihrer Anwendung wird in Bezug auf den Sitzungspersistenzkontext in einem der drei Hauptzustände angezeigt :

  • vorübergehend - diese Instanz ist nicht an eine Sitzung gebunden und war es auch nie . Diese Instanz hat keine entsprechenden Zeilen in der Datenbank. Es ist normalerweise nur ein neues Objekt, das Sie erstellt haben, um es in der Datenbank zu speichern.
  • persistent - Diese Instanz ist einem eindeutigen Sitzungsobjekt zugeordnet . Beim Leeren der Sitzung in die Datenbank wird garantiert, dass diese Entität einen entsprechenden konsistenten Datensatz in der Datenbank hat.
  • getrennt - Diese Instanz wurde einmal an eine Sitzung angehängt (in einem dauerhaften Zustand), jetzt jedoch nicht mehr. Eine Instanz wechselt in diesen Status, wenn Sie sie aus dem Kontext entfernen, die Sitzung löschen oder schließen oder die Instanz einem Serialisierungs- / Deserialisierungsprozess unterziehen.

Hier ist ein vereinfachtes Zustandsdiagramm mit Kommentaren zu Sitzungsmethoden , die die Zustandsübergänge ermöglichen.

Wenn sich die Entitätsinstanz im dauerhaften Zustand befindet, werden alle Änderungen, die Sie an den zugeordneten Feldern dieser Instanz vornehmen, beim Leeren der Sitzung auf die entsprechenden Datenbankeinträge und -felder angewendet . Die persistente Instanz kann als "online" betrachtet werden, während die getrennte Instanz "offline" gegangen ist und nicht auf Änderungen überwacht wird.

Dies bedeutet, dass Sie beim Ändern der Felder eines persistenten Objekts weder save , update noch eine dieser Methoden aufrufen müssen , um diese Änderungen in die Datenbank zu übertragen. Sie müssen lediglich die Transaktion festschreiben oder die Sitzung leeren oder schließen , wenn du damit fertig bist.

2.3. Konformität mit der JPA-Spezifikation

Der Ruhezustand war die erfolgreichste Java ORM-Implementierung. Kein Wunder, dass die Spezifikation für die Java Persistence API (JPA) stark von der Hibernate API beeinflusst wurde. Leider gab es auch viele Unterschiede: einige größere, andere subtilere.

Um als Implementierung des JPA-Standards zu fungieren, mussten die Hibernate-APIs überarbeitet werden. Der Sitzungsschnittstelle wurden mehrere Methoden hinzugefügt, die mit der EntityManager-Schnittstelle übereinstimmen. Diese Methoden dienen demselben Zweck wie die „ursprünglichen“ Methoden, entsprechen jedoch der Spezifikation und weisen daher einige Unterschiede auf.

3. Unterschiede zwischen den Operationen

Es ist wichtig, von Anfang an zu verstehen, dass alle Methoden ( persistieren , speichern , aktualisieren , zusammenführen , saveOrUpdate ) nicht sofort zu den entsprechenden SQL UPDATE- oder INSERT- Anweisungen führen. Das eigentliche Speichern von Daten in der Datenbank erfolgt beim Festschreiben der Transaktion oder beim Leeren der Sitzung .

Die genannten Methoden verwalten im Wesentlichen den Status von Entitätsinstanzen, indem sie zwischen verschiedenen Status entlang des Lebenszyklus übergehen.

Als Beispielentität verwenden wir eine einfache Entität mit Annotationszuordnung. Person :

@Entity public class Person { @Id @GeneratedValue private Long id; private String name; // ... getters and setters }

3.1. Fortdauern

Die persist- Methode dient zum Hinzufügen einer neuen Entitätsinstanz zum Persistenzkontext, dh zum Übergang einer Instanz vom vorübergehenden in den persistenten Zustand.

Wir nennen es normalerweise, wenn wir der Datenbank einen Datensatz hinzufügen möchten (eine Entitätsinstanz beibehalten):

Person person = new Person(); person.setName("John"); session.persist(person);

Was passiert nach dem Aufruf der Persist- Methode? Das Personenobjekt ist vom vorübergehenden in den dauerhaften Zustand übergegangen . Das Objekt befindet sich jetzt im Persistenzkontext, ist jedoch noch nicht in der Datenbank gespeichert. Die Generierung von INSERT- Anweisungen erfolgt nur beim Festschreiben der Transaktion, beim Löschen oder Schließen der Sitzung.

Beachten Sie, dass die persist- Methode den Rückgabetyp void hat . Es bearbeitet das übergebene Objekt "an Ort und Stelle" und ändert seinen Zustand. Die Personenvariable verweist auf das tatsächlich persistierte Objekt.

Diese Methode ist eine spätere Ergänzung der Sitzungsschnittstelle. Das Hauptunterscheidungsmerkmal dieser Methode besteht darin, dass sie der JSR-220-Spezifikation (EJB-Persistenz) entspricht. Die Semantik dieser Methode ist in der Spezifikation genau definiert, die im Wesentlichen besagt, dass:

  • Eine transiente Instanz wird persistent (und die Operation kaskadiert zu allen ihren Beziehungen mit cascade = PERSIST oder cascade = ALL ).
  • Wenn eine Instanz bereits persistent ist , hat dieser Aufruf keine Auswirkung auf diese bestimmte Instanz (aber er kaskadiert immer noch zu seinen Beziehungen mit cascade = PERSIST oder cascade = ALL ).
  • if an instance is detached, you should expect an exception, either upon calling this method, or upon committing or flushing the session.

Notice that there is nothing here that concerns the identifier of an instance. The spec does not state that the id will be generated right away, regardless of the id generation strategy. The specification for the persist method allows the implementation to issue statements for generating id on commit or flush, and the id is not guaranteed to be non-null after calling this method, so you should not rely upon it.

You may call this method on an already persistent instance, and nothing happens. But if you try to persist a detached instance, the implementation is bound to throw an exception. In the following example we persist the entity, evict it from the context so it becomes detached, and then try to persist again. The second call to session.persist() causes an exception, so the following code will not work:

Person person = new Person(); person.setName("John"); session.persist(person); session.evict(person); session.persist(person); // PersistenceException!

3.2. Save

The save method is an “original” Hibernate method that does not conform to the JPA specification.

Its purpose is basically the same as persist, but it has different implementation details. The documentation for this method strictly states that it persists the instance, “first assigning a generated identifier”. The method is guaranteed to return the Serializable value of this identifier.

Person person = new Person(); person.setName("John"); Long id = (Long) session.save(person);

The effect of saving an already persisted instance is the same as with persist. Difference comes when you try to save a detached instance:

Person person = new Person(); person.setName("John"); Long id1 = (Long) session.save(person); session.evict(person); Long id2 = (Long) session.save(person);

The id2 variable will differ from id1. The call of save on a detached instance creates a new persistent instance and assigns it a new identifier, which results in a duplicate record in a database upon committing or flushing.

3.3. Merge

The main intention of the merge method is to update a persistent entity instance with new field values from a detached entity instance.

For instance, suppose you have a RESTful interface with a method for retrieving an JSON-serialized object by its id to the caller and a method that receives an updated version of this object from the caller. An entity that passed through such serialization/deserialization will appear in a detached state.

After deserializing this entity instance, you need to get a persistent entity instance from a persistence context and update its fields with new values from this detached instance. So the merge method does exactly that:

  • finds an entity instance by id taken from the passed object (either an existing entity instance from the persistence context is retrieved, or a new instance loaded from the database);
  • copies fields from the passed object to this instance;
  • returns newly updated instance.

In the following example we evict (detach) the saved entity from context, change the name field, and then merge the detached entity.

Person person = new Person(); person.setName("John"); session.save(person); session.evict(person); person.setName("Mary"); Person mergedPerson = (Person) session.merge(person);

Note that the merge method returns an object — it is the mergedPerson object that was loaded into persistence context and updated, not the person object that you passed as an argument. Those are two different objects, and the person object usually needs to be discarded (anyway, don't count on it being attached to persistence context).

As with persist method, the merge method is specified by JSR-220 to have certain semantics that you can rely upon:

  • if the entity is detached, it is copied upon an existing persistent entity;
  • if the entity is transient, it is copied upon a newly created persistent entity;
  • this operation cascades for all relations with cascade=MERGE or cascade=ALL mapping;
  • if the entity is persistent, then this method call does not have effect on it (but the cascading still takes place).

3.4. Update

As with persist and save, the update method is an “original” Hibernate method that was present long before the merge method was added. Its semantics differs in several key points:

  • it acts upon passed object (its return type is void); the update method transitions the passed object from detached to persistent state;
  • this method throws an exception if you pass it a transient entity.

In the following example we save the object, then evict (detach) it from the context, then change its name and call update. Notice that we don't put the result of the update operation in a separate variable, because the update takes place on the person object itself. Basically we're reattaching the existing entity instance to the persistence context — something the JPA specification does not allow us to do.

Person person = new Person(); person.setName("John"); session.save(person); session.evict(person); person.setName("Mary"); session.update(person);

Trying to call update on a transient instance will result in an exception. The following will not work:

Person person = new Person(); person.setName("John"); session.update(person); // PersistenceException!

3.5. SaveOrUpdate

This method appears only in the Hibernate API and does not have its standardized counterpart. Similar to update, it also may be used for reattaching instances.

Actually, the internal DefaultUpdateEventListener class that processes the update method is a subclass of DefaultSaveOrUpdateListener, just overriding some functionality. The main difference of saveOrUpdate method is that it does not throw exception when applied to a transient instance; instead, it makes this transient instance persistent. The following code will persist a newly created instance of Person:

Person person = new Person(); person.setName("John"); session.saveOrUpdate(person);

You may think of this method as a universal tool for making an object persistent regardless of its state wether it is transient or detached.

4. What to Use?

If you don't have any special requirements, as a rule of thumb, you should stick to the persist and merge methods, because they are standardized and guaranteed to conform to the JPA specification.

They are also portable in case you decide to switch to another persistence provider, but they may sometimes appear not so useful as the “original” Hibernate methods, save, update and saveOrUpdate.

5. Conclusion

Wir haben den Zweck verschiedener Methoden für den Ruhezustand in Bezug auf die Verwaltung persistenter Entitäten zur Laufzeit erörtert. Wir haben gelernt, wie diese Methoden Entitätsinstanzen während ihres Lebenszyklus übertragen und warum einige dieser Methoden doppelte Funktionen haben.

Der Quellcode für den Artikel ist auf GitHub verfügbar.