Hibernate Entity Lifecycle

1. Panoramica

Ogni entità Hibernate ha naturalmente un ciclo di vita all'interno del framework: è in uno stato transitorio, gestito, scollegato o eliminato.

La comprensione di questi stati sia a livello concettuale che tecnico è essenziale per poter utilizzare correttamente Hibernate.

Per conoscere i vari metodi di Hibernate che gestiscono le entità, dai un'occhiata a uno dei nostri tutorial precedenti.

2. Metodi di supporto

In questo tutorial, utilizzeremo costantemente diversi metodi di supporto:

  • HibernateLifecycleUtil.getManagedEntities (sessione) - useremo per ottenere tutte le entità gestite da una di sessione negozio interno
  • DirtyDataInspector.getDirtyEntities (): utilizzeremo questo metodo per ottenere un elenco di tutte le entità contrassegnate come "sporche"
  • HibernateLifecycleUtil.queryCount (query): un metodo conveniente per eseguire query conteggio (*) sul database incorporato

Tutti i metodi di supporto di cui sopra vengono importati staticamente per una migliore leggibilità. Puoi trovare le loro implementazioni nel progetto GitHub collegato alla fine di questo articolo.

3. È tutta una questione di contesto di persistenza

Prima di entrare nell'argomento del ciclo di vita dell'entità, è innanzitutto necessario comprendere il contesto di persistenza .

In poche parole, il contesto di persistenza si trova tra il codice client e l'archivio dati . È un'area di staging in cui i dati persistenti vengono convertiti in entità, pronte per essere letti e modificati dal codice client.

Teoricamente parlando, il contesto di persistenza è un'implementazione del modello Unit of Work. Tiene traccia di tutti i dati caricati, tiene traccia delle modifiche di tali dati ed è responsabile di sincronizzare eventuali modifiche al database alla fine della transazione commerciale.

JPA EntityManager e Hibernate's Session sono un'implementazione del concetto di contesto di persistenza . In questo articolo, useremo Hibernate Session per rappresentare il contesto di persistenza.

Lo stato del ciclo di vita dell'entità di ibernazione spiega come l'entità è correlata a un contesto di persistenza , come vedremo in seguito.

4. Entità gestita

Un'entità gestita è una rappresentazione di una riga della tabella del database (sebbene quella riga non debba ancora esistere nel database).

Questo è gestito dalla Session attualmente in esecuzione , e ogni modifica apportata su di essa verrà tracciata e propagata automaticamente al database .

La sessione carica l'entità dal database o ricollega un'entità scollegata. Discuteremo delle entità separate nella sezione 5.

Osserviamo un po 'di codice per ottenere chiarimenti.

La nostra applicazione di esempio definisce un'entità, la classe FootballPlayer . All'avvio, inizializzeremo l'archivio dati con alcuni dati di esempio:

+-------------------+-------+ | Name | ID | +-------------------+-------+ | Cristiano Ronaldo | 1 | | Lionel Messi | 2 | | Gianluigi Buffon | 3 | +-------------------+-------+

Diciamo che per cominciare vogliamo cambiare il nome di Buffon: vogliamo mettere il suo nome completo Gianluigi Buffon invece di Gigi Buffon.

Per prima cosa, dobbiamo iniziare la nostra unità di lavoro ottenendo una sessione:

Session session = sessionFactory.openSession();

In un ambiente server, possiamo inserire una sessione nel nostro codice tramite un proxy sensibile al contesto. Il principio rimane lo stesso: abbiamo bisogno di una sessione per incapsulare la transazione commerciale della nostra unità di lavoro.

Successivamente, istruiremo la nostra sessione a caricare i dati dall'archivio persistente:

assertThat(getManagedEntities(session)).isEmpty(); List players = s.createQuery("from FootballPlayer").getResultList(); assertThat(getManagedEntities(session)).size().isEqualTo(3); 

Quando abbiamo prima ottenere una sessione , il suo negozio contesto persistente è vuota, come dimostra il nostro primo assert dichiarazione.

Successivamente, stiamo eseguendo una query che recupera i dati dal database, crea la rappresentazione di entità dei dati e infine restituisce l'entità da utilizzare.

Internamente, la sessione tiene traccia di tutte le entità caricate nell'archivio di contesto persistente. Nel nostro caso, l' archivio interno della sessione conterrà 3 entità dopo la query.

Ora cambiamo il nome di Gigi:

Transaction transaction = session.getTransaction(); transaction.begin(); FootballPlayer gigiBuffon = players.stream() .filter(p -> p.getId() == 3) .findFirst() .get(); gigiBuffon.setName("Gianluigi Buffon"); transaction.commit(); assertThat(getDirtyEntities()).size().isEqualTo(1); assertThat(getDirtyEntities().get(0).getName()).isEqualTo("Gianluigi Buffon");

4.1. Come funziona?

Alla chiamata alla transazione commit () o flush () , la sessione troverà tutte le entità sporche dal suo elenco di monitoraggio e sincronizzerà lo stato con il database.

Si noti che non è stato necessario chiamare alcun metodo per notificare a Session che abbiamo modificato qualcosa nella nostra entità: poiché è un'entità gestita, tutte le modifiche vengono propagate automaticamente al database.

Un'entità gestita è sempre un'entità persistente: deve avere un identificatore di database, anche se la rappresentazione della riga del database non è ancora stata creata, ovvero l'istruzione INSERT è in attesa della fine dell'unità di lavoro.

Vedere il capitolo sulle entità transitorie di seguito.

5. Entità distaccata

Un'entità indipendente è solo un POJO un'entità ordinario cui identità valore corrisponde ad una riga di database. La differenza rispetto a un'entità gestita è che non viene più tracciata da alcun contesto di persistenza .

Un'entità può staccarsi quando la Session usata per caricarla è stata chiusa, o quando chiamiamo Session.evict (entity) o Session.clear () .

Vediamolo nel codice:

FootballPlayer cr7 = session.get(FootballPlayer.class, 1L); assertThat(getManagedEntities(session)).size().isEqualTo(1); assertThat(getManagedEntities(session).get(0).getId()).isEqualTo(cr7.getId()); session.evict(cr7); assertThat(getManagedEntities(session)).size().isEqualTo(0);

Il nostro contesto di persistenza non terrà traccia dei cambiamenti nelle entità separate:

cr7.setName("CR7"); transaction.commit(); assertThat(getDirtyEntities()).isEmpty();

Session.merge (entity) /Session.update (entity) può (ri) allegare una sessione :

FootballPlayer messi = session.get(FootballPlayer.class, 2L); session.evict(messi); messi.setName("Leo Messi"); transaction.commit(); assertThat(getDirtyEntities()).isEmpty(); transaction = startTransaction(session); session.update(messi); transaction.commit(); assertThat(getDirtyEntities()).size().isEqualTo(1); assertThat(getDirtyEntities().get(0).getName()).isEqualTo("Leo Messi");

Per riferimento sia su Session.merge () che su Session.update () vedere qui.

5.1. Il campo dell'identità è tutto ciò che conta

Diamo un'occhiata alla seguente logica:

FootballPlayer gigi = new FootballPlayer(); gigi.setId(3); gigi.setName("Gigi the Legend"); session.update(gigi);

Nell'esempio sopra, abbiamo istanziato un'entità nel solito modo tramite il suo costruttore. Abbiamo popolato i campi con valori e abbiamo impostato l'identità a 3, che corrisponde all'identità dei dati persistenti che appartengono a Gigi Buffon. La chiamata di update () ha esattamente lo stesso effetto come se avessimo caricato l'entità da un altro contesto di persistenza .

In effetti, Session non distingue da dove ha avuto origine un'entità ricollegata.

È uno scenario abbastanza comune nelle applicazioni Web costruire entità separate dai valori del modulo HTML.

Per quanto riguarda Session , un'entità distaccata è solo un'entità semplice il cui valore di identità corrisponde a dati persistenti.

Be aware that the example above just serves a demo purpose. and we need to know exactly what we're doing. Otherwise, we could end up with null values across our entity if we just set the value on the field we want to update, leaving the rest untouched (so, effectively null).

6. Transient Entity

A transient entity is simply an entity object that has no representation in the persistent store and is not managed by any Session.

A typical example of a transient entity would be instantiating a new entity via its constructor.

To make a transient entity persistent, we need to call Session.save(entity) or Session.saveOrUpdate(entity):

FootballPlayer neymar = new FootballPlayer(); neymar.setName("Neymar"); session.save(neymar); assertThat(getManagedEntities(session)).size().isEqualTo(1); assertThat(neymar.getId()).isNotNull(); int count = queryCount("select count(*) from Football_Player where name="Neymar""); assertThat(count).isEqualTo(0); transaction.commit(); count = queryCount("select count(*) from Football_Player where name="Neymar""); assertThat(count).isEqualTo(1);

As soon as we execute Session.save(entity), the entity is assigned an identity value and becomes managed by the Session. However, it might not yet be available in the database as the INSERT operation might be delayed until the end of the unit of work.

7. Deleted Entity

An entity is in a deleted (removed) state if Session.delete(entity) has been called, and the Session has marked the entity for deletion. The DELETE command itself might be issued at the end of the unit of work.

Let's see it in the following code:

session.delete(neymar); assertThat(getManagedEntities(session).get(0).getStatus()).isEqualTo(Status.DELETED);

However, notice that the entity stays in the persistent context store until the end of the unit of work.

8. Conclusion

Il concetto di contesto di persistenza è centrale per comprendere il ciclo di vita delle entità Hibernate. Abbiamo chiarito il ciclo di vita esaminando gli esempi di codice che dimostrano ogni stato.

Come al solito, il codice utilizzato in questo articolo può essere trovato su GitHub.