Hibernate: save, persist, update, merge, saveOrUpdate

Introduzione

In questo articolo discuteremo le differenze tra i diversi metodi dell’interfaccia di sessione: 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.

Ulteriori letture:

Eliminazione di oggetti con Hibernate

Guida rapida all’eliminazione di un’entità in Hibernate.
Per saperne di più →

Stored Procedure con Hibernate

Questo articolo discute brevemente come chiamare le procedure di archiviazione da Hibernate.
Per saperne di più →

Una panoramica degli identificatori in Hibernate / JPA

Scopri come mappare gli identificatori di entità con Hibernate.
Per saperne di più →

Sessione come implementazione del contesto di persistenza

L’interfaccia di sessione 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à

Oltre alla 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 si lavora con il database attraverso un contesto di persistenza e tutte le istanze di entità sono collegate a questo contesto, è sempre necessario disporre di una singola istanza di entità per ogni record di database con cui si è interagito durante la sessione.

In Hibernate, il contesto di persistenza è rappresentato da org.ibernazione.Istanza di sessione. Per JPA, è il javax.persistenza.EntityManager. Quando usiamo Hibernate come provider JPA e operiamo tramite l’interfaccia EntityManager, l’implementazione di questa interfaccia avvolge fondamentalmente l’oggetto di sessione sottostante. Tuttavia, Hibernate Session fornisce un’interfaccia più ricca con più possibilità, quindi a volte è utile lavorare direttamente con Session.

2.2. Stati di Istanze di Entità

Ogni istanza dell’entità dell’applicazione viene visualizzata in uno dei tre stati principali in relazione alla persistenza della Sessione contesto:

  • transitorio — questa istanza non è, e non c’è mai stato collegato a una Sessione; questo esempio non ha le righe corrispondenti nel database; di solito si tratta solo di un nuovo oggetto che è stato creato per salvare nel database;
  • persistente — questa istanza è associato con un unico oggetto di Sessione; dopo il risciacquo la Sessione al database, questo ente è garantito per avere un corrispondente coerente record nel database;
  • bifamiliare — in questa istanza era una volta collegato a una Sessione (in un persistente stato), ma ora non è; un’istanza entra in questo stato se si rimuove dal contesto, cancellare o chiudere la Sessione, o inserire l’istanza attraverso la serializzazione/deserializzazione del processo.

Ecco un diagramma di stato semplificato con commenti sui metodi di sessione che rendono possibili le transizioni di stato.

2016-07-11_13-38-11

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 al momento dello svuotamento della Sessione. L’istanza persistente può essere considerata “online”, mentre l’istanza distaccata è diventata “offline” e non viene monitorata per le modifiche.

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

2.3. La conformità alle specifiche 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 agire come implementazione dello standard JPA, le API di Hibernate dovevano essere riviste. Diversi metodi sono stati aggiunti all’interfaccia di sessione per abbinare l’interfaccia EntityManager. Questi metodi hanno lo stesso scopo dei metodi “originali”, ma sono conformi alle specifiche e quindi hanno alcune differenze.

Differenze tra le operazioni

È importante capire fin dall’inizio che tutti i metodi (persist, save, update, merge, saveOrUpdate) non producono immediatamente le corrispondenti istruzioni SQL UPDATE o INSERT. Il salvataggio effettivo dei dati nel database si verifica 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 persona di entità mappata con annotazione:

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

3.1. Persist

Il metodo persist è destinato all’aggiunta di una nuova istanza di entità al contesto di persistenza, ovvero alla 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 person è passato da uno stato transitorio a uno persistente. L’oggetto si trova ora nel contesto di persistenza, ma non è ancora stato salvato nel database. La generazione delle istruzioni INSERT avverrà solo al momento dell’avvio della transazione, dello svuotamento o della chiusura della sessione.

Si noti che il metodo persist ha un tipo di ritorno void. Opera sull’oggetto passato “sul posto”, cambiando il suo stato. La variabile person fa riferimento all’oggetto persistente effettivo.

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

  • un transitorio istanza diventa persistente (e l’operazione cascate per tutti i suoi rapporti con cascade=PERSISTONO o cascade=TUTTI),
  • se l’istanza è già persistente, quindi questo appello non ha effetto per questo caso particolare (ma ancora cascate ai suoi rapporti con cascade=PERSISTONO o cascade=TUTTI),
  • se un’istanza indipendente, si dovrebbe aspettare un’eccezione, sia al momento di chiamare questo metodo o su commettere o vampate di calore la sessione.

Si noti che qui non c’è nulla che riguardi l’identificatore di un’istanza. Le specifiche non indicano che l’ID verrà generato immediatamente, indipendentemente dalla strategia di generazione dell’ID. La specifica per il metodo persist consente all’implementazione di emettere istruzioni per la generazione di id su commit o flush e l’id non è garantito per essere non null dopo aver chiamato questo metodo, quindi non è necessario fare affidamento su di esso.

Puoi chiamare questo metodo su un’istanza già persistente e non succede nulla. Ma se si tenta di mantenere un’istanza distaccata, l’implementazione è destinata a generare un’eccezione. Nell’esempio seguente persistiamo l’entità, la sfrattiamo dal contesto in modo che si distacchi e quindi proviamo a persistere di nuovo. La seconda chiamata alla sessione.persist () causa un’eccezione, quindi il seguente codice non funzionerà:

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

3.2. Save

Il metodo save è un metodo di ibernazione “originale” che non è conforme alle specifiche JPA.

Il suo scopo è fondamentalmente lo stesso di persist, ma ha diversi dettagli di implementazione. La documentazione per questo metodo afferma rigorosamente che persiste l’istanza, “prima assegnando un identificatore generato”. Il metodo è garantito per restituire il valore serializzabile di questo identificatore.

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

L’effetto del salvataggio di un’istanza già persistente è lo stesso di persist. La differenza arriva quando si tenta di salvare un’istanza distaccata:

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

La variabile id2 sarà diversa da id1. La chiamata di save su un’istanza distaccata crea una nuova istanza persistente e le assegna un nuovo identificatore, che si traduce in un record duplicato in un database al momento del commit o del flushing.

3.3. Merge

L’intenzione principale del metodo merge è aggiornare un’istanza di entità persistente con nuovi valori di campo da un’istanza di entità distaccata.

Ad esempio, supponiamo di avere un’interfaccia RESTful con un metodo per recuperare un oggetto serializzato JSON dal suo ID al chiamante e un metodo che riceve una versione aggiornata di questo oggetto dal chiamante. Un’entità che è passata attraverso tale serializzazione / deserializzazione apparirà in uno stato distaccato.

Dopo aver deserializzato questa istanza di entità, è necessario ottenere un’istanza di entità persistente da un contesto di persistenza e aggiornare i suoi campi con nuovi valori da questa istanza distaccata. Quindi il metodo di unione fa esattamente questo:

  • trova un’istanza di entità per id prelevato dall’oggetto passato (viene recuperata un’istanza di entità esistente dal contesto di persistenza o una nuova istanza caricata dal database);
  • copia i campi dall’oggetto passato a questa istanza;
  • restituisce l’istanza appena aggiornata.

Nell’esempio seguente sfrattiamo (stacchiamo) l’entità salvata dal contesto, cambiamo il campo nome e quindi uniamo l’entità distaccata.

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

Si noti che il metodo merge restituisce un oggetto: è l’oggetto mergedPerson che è stato caricato nel contesto di persistenza e aggiornato, non l’oggetto person passato come argomento. Questi sono due oggetti diversi e l’oggetto person di solito deve essere scartato (comunque, non contare sul fatto che sia collegato al contesto di persistenza).

Come con il persistere metodo, il metodo merge è specificato da JSR-220 per avere la semantica di alcuni che si può fare affidamento su:

  • se l’entità è staccata, viene copiato su un permanente esistente entità;
  • se l’entità è transitoria, viene copiato su un nuovo persistente entità;
  • questa operazione cascate per tutti i rapporti con cascade=UNIONE o in cascata=TUTTI i tipi di mapping;
  • se l’entità è persistente, quindi la chiamata a questo metodo non hanno effetto su di esso (ma la propagazione avviene ancora).

3.4. Update

Come con persist e save, il metodo update è un metodo di ibernazione “originale” che era presente molto prima che venisse aggiunto il metodo merge. La sua semantica differisce in diversi punti chiave:

  • agisce sull’oggetto passato (il suo tipo restituito è nullo); il metodo di aggiornamento passa l’oggetto passato dallo stato staccato a quello persistente;
  • questo metodo genera un’eccezione se lo si passa a un’entità transitoria.

Nel seguente esempio salviamo l’oggetto, quindi lo sfrattiamo (staccandolo) dal contesto, quindi cambiamo il suo nome e chiamiamo update. Si noti che non inseriamo il risultato dell’operazione di aggiornamento in una variabile separata, perché l’aggiornamento avviene sull’oggetto person stesso. Fondamentalmente stiamo ricollegando l’istanza di entità esistente al contesto di persistenza — qualcosa che la specifica JPA non ci consente di fare.

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

Provare a chiamare update su un’istanza transitoria comporterà un’eccezione. Quanto segue non funzionerà:

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

3.5. SaveOrUpdate

Questo metodo viene visualizzato solo nell’API Hibernate e non ha la sua controparte standardizzata. Simile a update, può anche essere utilizzato per riattaccare le istanze.

In realtà, la classe DefaultUpdateEventListener interna che elabora il metodo update è una sottoclasse di DefaultSaveOrUpdateListener, che sostituisce solo alcune funzionalità. La differenza principale del metodo saveOrUpdate è che non genera eccezioni quando applicato a un’istanza transitoria; invece, rende questa istanza transitoria persistente. Il seguente codice persisterà un’istanza di Persona appena creata:

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

Potresti pensare a questo metodo come uno strumento universale per rendere un oggetto persistente indipendentemente dal suo stato se è transitorio o distaccato.

Cosa usare?

Se non si dispone di requisiti speciali, come regola generale, è necessario attenersi ai metodi persist e merge, poiché sono standardizzati e garantiti per essere conformi alle specifiche JPA.

Sono anche portatili nel caso in cui si decida di passare a un altro provider di persistenza, ma a volte possono sembrare non così utili come i metodi di ibernazione “originali”, save, update e saveOrUpdate.

Conclusione

Abbiamo discusso lo scopo di diversi metodi di sessione di ibernazione in relazione alla gestione di entità persistenti in runtime. Abbiamo imparato come questi metodi transist istanze di entità attraverso i loro cicli di vita e perché alcuni di questi metodi hanno funzionalità duplicate.



+