Hibernate non ha potuto inizializzare il proxy - nessuna sessione

1. Panoramica

Lavorando con Hibernate, potremmo aver riscontrato un errore che dice: org.hibernate.LazyInitializationException: impossibile inizializzare il proxy - nessuna sessione .

In questo rapido tutorial, daremo uno sguardo più da vicino alla causa principale dell'errore e impareremo come evitarlo.

2 Comprensione dell'errore

L'accesso a un oggetto con caricamento lento al di fuori del contesto di una sessione Hibernate aperta comporterà questa eccezione.

È importante capire cosa sono Session , Lazy Initialisation e Proxy Object e come si uniscono nel framework Hibernate .

  • La sessione è un contesto di persistenza che rappresenta una conversazione tra un'applicazione e il database
  • Lazy Loading significa che l'oggetto non verrà caricato nel contesto della sessione finché non vi si accede nel codice.
  • Hibernate crea una sottoclasse dinamica di oggetti proxy che raggiungerà il database solo quando utilizzeremo l'oggetto per la prima volta.

Questo errore significa che proviamo a recuperare un oggetto caricato in modo lento dal database utilizzando un oggetto proxy, ma la sessione di Hibernate è già chiusa.

3. Esempio per LazyInitializationException

Vediamo l'eccezione in uno scenario concreto.

Vogliamo creare un semplice oggetto Utente con ruoli associati. Usiamo JUnit per dimostrare l' errore LazyInitializationException .

3.1. Hibernate Utility Class

Per prima cosa, definiamo una classe HibernateUtil per creare una SessionFactory con configurazione.

Useremo il database HSQLDB in memoria .

3.2. Entità

Ecco la nostra entità utente :

@Entity @Table(name = "user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private int id; @Column(name = "first_name") private String firstName; @Column(name = "last_name") private String lastName; @OneToMany private Set roles; } 

E l' entità di ruolo associata :

@Entity @Table(name = "role") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private int id; @Column(name = "role_name") private String roleName; }

Come possiamo vedere, esiste una relazione uno-a-molti tra Utente e Ruolo .

3.3. Creazione di utenti con ruoli

Successivamente, creiamo due oggetti Ruolo :

Role admin = new Role("Admin"); Role dba = new Role("DBA");

Quindi, creiamo un utente con i ruoli:

User user = new User("Bob", "Smith"); user.addRole(admin); user.addRole(dba);

Infine, possiamo aprire una sessione e rendere persistenti gli oggetti:

Session session = sessionFactory.openSession(); session.beginTransaction(); user.getRoles().forEach(role -> session.save(role)); session.save(user); session.getTransaction().commit(); session.close();

3.4. Recupero ruoli

Nel primo scenario, vedremo come recuperare i ruoli utente in modo corretto:

@Test public void whenAccessUserRolesInsideSession_thenSuccess() { User detachedUser = createUserWithRoles(); Session session = sessionFactory.openSession(); session.beginTransaction(); User persistentUser = session.find(User.class, detachedUser.getId()); Assert.assertEquals(2, persistentUser.getRoles().size()); session.getTransaction().commit(); session.close(); }

Qui accediamo all'oggetto all'interno della sessione, quindi non ci sono errori.

3.5. Recupero ruoli non riuscito

Nel secondo scenario, chiameremo un metodo getRoles al di fuori della sessione:

@Test public void whenAccessUserRolesOutsideSession_thenThrownException() { User detachedUser = createUserWithRoles(); Session session = sessionFactory.openSession(); session.beginTransaction(); User persistentUser = session.find(User.class, detachedUser.getId()); session.getTransaction().commit(); session.close(); thrown.expect(LazyInitializationException.class); System.out.println(persistentUser.getRoles().size()); }

In tal caso, proviamo ad accedere ai ruoli dopo la chiusura della sessione e, di conseguenza, il codice genera un'eccezione LazyInitializationException .

4. Come evitare l'errore

Diamo un'occhiata a quattro diverse soluzioni per superare l'errore.

4.1. Apri sessione nel livello superiore

La procedura migliore è aprire una sessione nel livello di persistenza, ad esempio utilizzando il pattern DAO.

Possiamo aprire la sessione negli strati superiori per accedere agli oggetti associati in modo sicuro. Ad esempio, possiamo aprire la sessione nel livello Visualizza .

Di conseguenza, vedremo un aumento del tempo di risposta, che influirà sulle prestazioni dell'applicazione.

Questa soluzione è un anti-modello in termini di principio di separazione delle preoccupazioni. Inoltre, può causare violazioni dell'integrità dei dati e transazioni di lunga durata.

4.2. Attivazione della proprietà enable_lazy_load_no_trans

Questa proprietà Hibernate viene utilizzata per dichiarare una politica globale per il recupero di oggetti con caricamento lento.

Per impostazione predefinita, questa proprietà è false . Attivarlo significa che ogni accesso a un'entità con caricamento lento associato verrà inserito in una nuova sessione in esecuzione in una nuova transazione:

Using this property to avoid LazyInitializationException error is not recommended since it will slow down the performance of our application. This is because we'll end up with an n + 1 problem. Simply put, that means one SELECT for the User and N additional SELECTs to fetch the roles of each user.

This approach is not efficient and also considered an anti-pattern.

4.3. Using FetchType.EAGER Strategy

We can use this strategy along with a @OneToMany annotation, for example :

@OneToMany(fetch = FetchType.EAGER) @JoinColumn(name = "user_id") private Set roles;

This is a kind of compromised solution for a particular usage when we need to fetch the associated collection for most of our use cases.

So it's much easier to declare the EAGER fetch type instead of explicitly fetching the collection for most of the different business flows.

4.4. Using Join Fetching

We can use a JOIN FETCH directive in JPQL to fetch the associated collection on-demand, for example :

SELECT u FROM User u JOIN FETCH u.roles

Or we can use the Hibernate Criteria API :

Criteria criteria = session.createCriteria(User.class); criteria.setFetchMode("roles", FetchMode.EAGER);

Here, we specify the associated collection that should be fetched from the database along with the User object on the same round trip. Using this query improves the efficiency of iteration since it eliminates the need for retrieving the associated objects separately.

This is the most efficient and fine-grained solution to avoid the LazyInitializationException error.

5. Conclusion

In questo articolo, abbiamo visto come gestire org.hibernate.LazyInitializationException: impossibile inizializzare il proxy - nessun errore di sessione .

Abbiamo esplorato diversi approcci insieme a problemi di prestazioni. È importante utilizzare una soluzione semplice ed efficiente per evitare di influire sulle prestazioni.

Infine, abbiamo visto come l'approccio del join-fetch sia un buon modo per evitare l'errore.

Come sempre, il codice è disponibile su GitHub.