Ibernazione: salva, persist, aggiorna, unisci, saveOrUpdate

1. Introduzione

In questo articolo discuteremo le differenze tra diversi metodi dell'interfaccia Session : save , persist , update , merge , saveOrUpdate .

Questa non è un'introduzione a Hibernate e dovresti già conoscere le basi della configurazione, della mappatura relazionale degli oggetti e del lavoro con le istanze di entità. Per un articolo introduttivo a Hibernate, visita il nostro tutorial su Hibernate 4 con Spring.

2. Sessione come implementazione del contesto di persistenza

L' interfaccia Session ha diversi metodi che alla fine portano al salvataggio dei dati nel database: persist , save , update , merge , saveOrUpdate . Per comprendere la differenza tra questi metodi, dobbiamo prima discutere lo scopo della Sessione come contesto di persistenza e la differenza tra gli stati delle istanze di entità in relazione alla Sessione .

Dovremmo anche comprendere la storia dello sviluppo di Hibernate che ha portato ad alcuni metodi API parzialmente duplicati.

2.1. Gestione delle istanze di entità

A parte la stessa mappatura relazionale degli oggetti, uno dei problemi che Hibernate intendeva risolvere è il problema della gestione delle entità durante il runtime. La nozione di "contesto di persistenza" è la soluzione di Hibernate a questo problema. Il contesto di persistenza può essere pensato come un contenitore o una cache di primo livello per tutti gli oggetti caricati o salvati in un database durante una sessione.

La sessione è una transazione logica, i cui limiti sono definiti dalla logica di business dell'applicazione. Quando lavori con il database attraverso un contesto di persistenza e tutte le tue istanze di entità sono collegate a questo contesto, dovresti sempre avere una singola istanza di entità per ogni record di database con cui hai interagito durante la sessione.

In Hibernate, il contesto di persistenza è rappresentato dall'istanza org.hibernate.Session . Per JPA, è javax.persistence.EntityManager . Quando utilizziamo Hibernate come provider JPA e operiamo tramite l' interfaccia EntityManager , l'implementazione di questa interfaccia fondamentalmente avvolge l' oggetto Session sottostante . Tuttavia, Hibernate Session fornisce un'interfaccia più ricca con più possibilità, quindi a volte è utile lavorare direttamente con Session .

2.2. Istanze di Stati di entità

Qualsiasi istanza di entità nella tua applicazione appare in uno dei tre stati principali in relazione al contesto di persistenza della sessione :

  • transitorio : questa istanza non è e non è mai stata allegata a una sessione ; questa istanza non ha righe corrispondenti nel database; di solito è solo un nuovo oggetto che hai creato per salvare nel database;
  • persistente : questa istanza è associata a un oggetto Session univoco ; quando si scarica la sessione nel database, è garantito che questa entità disponga di un record coerente corrispondente nel database;
  • disconnesso - questa istanza una volta era collegata a una sessione (in uno stato persistente ), ma ora non lo è; un'istanza entra in questo stato se la si rimuove dal contesto, si cancella o si chiude la sessione o si sottopone l'istanza al processo di serializzazione / deserializzazione.

Di seguito è riportato un diagramma di stato semplificato con commenti sui metodi di sessione che consentono di eseguire le transizioni di stato.

Quando l'istanza dell'entità è nello stato persistente , tutte le modifiche apportate ai campi mappati di questa istanza verranno applicate ai record e ai campi del database corrispondenti dopo lo svuotamento della sessione . L' istanza persistente può essere considerata "online", mentre l' istanza scollegata è diventata "offline" e non viene monitorata per eventuali modifiche.

Ciò significa che quando modifichi i campi di un oggetto persistente , non devi chiamare save , update o nessuno di questi metodi per ottenere queste modifiche al database: tutto ciò di cui hai bisogno è eseguire il commit della transazione, o svuotare o chiudere la sessione , quando hai finito.

2.3. Conformità alla specifica JPA

Hibernate è stata l'implementazione ORM Java di maggior successo. Non c'è da stupirsi che la specifica per Java Persistence API (JPA) sia stata fortemente influenzata dall'API Hibernate. Sfortunatamente, c'erano anche molte differenze: alcune importanti, altre più sottili.

Per fungere da implementazione dello standard JPA, le API Hibernate dovevano essere riviste. Sono stati aggiunti diversi metodi all'interfaccia Session per abbinare l'interfaccia EntityManager. Questi metodi hanno lo stesso scopo dei metodi "originali", ma sono conformi alle specifiche e quindi presentano alcune differenze.

3. Differenze tra le operazioni

È importante capire dall'inizio che tutti i metodi ( persist , save , update , merge , saveOrUpdate ) non danno immediatamente come risultato le corrispondenti istruzioni SQL UPDATE o INSERT . L'effettivo salvataggio dei dati nel database avviene al momento del commit della transazione o dello svuotamento della sessione .

I metodi menzionati gestiscono fondamentalmente lo stato delle istanze di entità trasferendole tra stati diversi lungo il ciclo di vita.

Come entità di esempio, useremo una semplice entità mappata con annotazioni Person :

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

3.1. Persistere

Il metodo persist ha lo scopo di aggiungere una nuova istanza di entità al contesto di persistenza, ovvero la transizione di un'istanza dallo stato transitorio a quello persistente .

Di solito lo chiamiamo quando vogliamo aggiungere un record al database (persistere un'istanza di entità):

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

Cosa succede dopo che viene chiamato il metodo persist ? L' oggetto persona è passato dallo stato transitorio a quello persistente . L'oggetto è ora nel contesto di persistenza, ma non è ancora stato salvato nel database. La generazione delle istruzioni INSERT avverrà solo al momento del commit della transazione, dello scaricamento o della chiusura della sessione.

Si noti che il metodo persist ha un tipo restituito void . Agisce sull'oggetto passato “in place”, cambiandone lo stato. La variabile persona fa riferimento all'oggetto persistente effettivo.

Questo metodo è un'aggiunta successiva all'interfaccia Session. La principale caratteristica di differenziazione di questo metodo è che è conforme alla specifica JSR-220 (persistenza EJB). La semantica di questo metodo è rigorosamente definita nella specifica, che sostanzialmente afferma che:

  • un transiente un'istanza diventa persistente (e le cascate di funzionamento a tutti i suoi rapporti con cascata = PERSIST o cascade = ALL ),
  • se un'istanza è già persistente , allora questa chiamata non ha effetto per questa particolare istanza (ma continua a collegarsi alle sue relazioni con cascade = PERSIST o 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

Abbiamo discusso lo scopo dei diversi metodi di Hibernate Session in relazione alla gestione di entità persistenti in runtime. Abbiamo appreso come questi metodi transistano le istanze di entità attraverso i loro cicli di vita e perché alcuni di questi metodi hanno funzionalità duplicate.

Il codice sorgente dell'articolo è disponibile su GitHub.