Stati oggetto nella sessione di Hibernate

1. Introduzione

Hibernate è un comodo framework per la gestione dei dati persistenti, ma a volte capire come funziona internamente può essere complicato.

In questo tutorial, impareremo gli stati degli oggetti e come spostarsi tra di loro. Vedremo anche i problemi che possiamo incontrare con entità distaccate e come risolverli.

2. Hibernate's Session

L' interfaccia Session è lo strumento principale utilizzato per comunicare con Hibernate. Fornisce un'API che ci consente di creare, leggere, aggiornare ed eliminare oggetti persistenti. La sessione ha un ciclo di vita semplice. Lo apriamo, eseguiamo alcune operazioni e poi lo chiudiamo.

Quando operiamo sugli oggetti durante la sessione , si attaccano a quella sessione . Le modifiche apportate vengono rilevate e salvate alla chiusura. Dopo la chiusura, Hibernate interrompe le connessioni tra gli oggetti e la sessione.

3. Stati oggetto

Nel contesto della sessione di Hibernate , gli oggetti possono trovarsi in uno dei tre possibili stati: transitorio, persistente o distaccato.

3.1. Transitorio

Un oggetto che non abbiamo associato a nessuna sessione è nello stato transitorio. Poiché non è mai stato persistente, non ha alcuna rappresentazione nel database. Poiché nessuna sessione ne è a conoscenza, non verrà salvata automaticamente.

Creiamo un oggetto utente con il costruttore e confermiamo che non è gestito dalla sessione:

Session session = openSession(); UserEntity userEntity = new UserEntity("John"); assertThat(session.contains(userEntity)).isFalse();

3.2. Persistente

Un oggetto che abbiamo associato a una sessione si trova nello stato persistente. L'abbiamo salvato o letto da un contesto di persistenza, quindi rappresenta una riga nel database.

Creiamo un oggetto e quindi utilizziamo il metodo persist per renderlo persistente:

Session session = openSession(); UserEntity userEntity = new UserEntity("John"); session.persist(userEntity); assertThat(session.contains(userEntity)).isTrue();

In alternativa, possiamo utilizzare il metodo di salvataggio . La differenza è che il metodo persist salverà solo un oggetto e il metodo save genererà inoltre il suo identificatore se necessario.

3.3. Distaccato

Quando chiudiamo la sessione , tutti gli oggetti al suo interno si staccano. Sebbene rappresentino ancora righe nel database, non sono più gestite da alcuna sessione :

session.persist(userEntity); session.close(); assertThat(session.isOpen()).isFalse(); assertThatThrownBy(() -> session.contains(userEntity));

Successivamente, impareremo come salvare entità transitorie e scollegate.

4. Salvataggio e ricollegamento di un'entità

4.1. Salvataggio di un'entità transitoria

Creiamo una nuova entità e salviamola nel database. Quando costruiamo l'oggetto per la prima volta, sarà nello stato transitorio.

Per rendere persistente la nostra nuova entità, useremo il metodo persist :

UserEntity userEntity = new UserEntity("John"); session.persist(userEntity);

Ora creeremo un altro oggetto con lo stesso identificatore del primo. Questo secondo oggetto è transitorio perché non è ancora gestito da nessuna sessione , ma non possiamo renderlo persistente utilizzando il metodo persist . È già rappresentato nel database, quindi non è veramente nuovo nel contesto del livello di persistenza.

Invece, useremo il metodo di unione per aggiornare il database e rendere l'oggetto persistente :

UserEntity onceAgainJohn = new UserEntity("John"); session.merge(onceAgainJohn);

4.2. Salvataggio di un'entità distaccata

Se chiudiamo la sessione precedente , i nostri oggetti saranno in uno stato distaccato. Analogamente all'esempio precedente, sono rappresentati nel database ma non sono attualmente gestiti da alcuna sessione . Possiamo renderli di nuovo persistenti usando il metodo di unione :

UserEntity userEntity = new UserEntity("John"); session.persist(userEntity); session.close(); session.merge(userEntity);

5. Entità annidate

Le cose si complicano se consideriamo le entità annidate. Diciamo che la nostra entità utente memorizzerà anche le informazioni sul suo manager:

public class UserEntity { @Id private String name; @ManyToOne private UserEntity manager; }

Quando salviamo questa entità, dobbiamo pensare non solo allo stato dell'entità stessa ma anche allo stato dell'entità annidata. Creiamo un'entità utente persistente e quindi impostiamo il suo gestore:

UserEntity userEntity = new UserEntity("John"); session.persist(userEntity); UserEntity manager = new UserEntity("Adam"); userEntity.setManager(manager);

Se proviamo ad aggiornarlo ora, otterremo un'eccezione:

assertThatThrownBy(() -> { session.saveOrUpdate(userEntity); transaction.commit(); });
java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.baeldung.states.UserEntity.manager -> com.baeldung.states.UserEntity 

Ciò sta accadendo perché Hibernate non sa cosa fare con l'entità annidata transitoria.

5.1. Entità annidate persistenti

Un modo per risolvere questo problema è rendere persistenti le entità annidate in modo esplicito:

UserEntity manager = new UserEntity("Adam"); session.persist(manager); userEntity.setManager(manager);

Quindi, dopo aver eseguito il commit della transazione, saremo in grado di recuperare l'entità salvata correttamente:

transaction.commit(); session.close(); Session otherSession = openSession(); UserEntity savedUser = otherSession.get(UserEntity.class, "John"); assertThat(savedUser.getManager().getName()).isEqualTo("Adam");

5.2. Operazioni a cascata

Le entità annidate transitorie possono essere mantenute automaticamente se configuriamo correttamente la proprietà a cascata della relazione nella classe di entità:

@ManyToOne(cascade = CascadeType.PERSIST) private UserEntity manager;

Ora, quando persistiamo l'oggetto, tale operazione verrà applicata a cascata a tutte le entità annidate:

UserEntityWithCascade userEntity = new UserEntityWithCascade("John"); session.persist(userEntity); UserEntityWithCascade manager = new UserEntityWithCascade("Adam"); userEntity.setManager(manager); // add transient manager to persistent user session.saveOrUpdate(userEntity); transaction.commit(); session.close(); Session otherSession = openSession(); UserEntityWithCascade savedUser = otherSession.get(UserEntityWithCascade.class, "John"); assertThat(savedUser.getManager().getName()).isEqualTo("Adam");

6. Riepilogo

In questo tutorial, abbiamo esaminato più da vicino come funziona la sessione di ibernazione rispetto allo stato dell'oggetto. Abbiamo quindi esaminato alcuni problemi che può creare e come risolverli.

Come sempre, il codice sorgente è disponibile su GitHub.