Der Ruhezustand konnte den Proxy nicht initialisieren - keine Sitzung

1. Übersicht

Bei der Arbeit mit Hibernate ist möglicherweise ein Fehler aufgetreten , der besagt: org.hibernate.LazyInitializationException: Proxy konnte nicht initialisiert werden - keine Sitzung .

In diesem kurzen Tutorial werden wir uns die Grundursache des Fehlers genauer ansehen und lernen, wie Sie ihn vermeiden können.

2 Den Fehler verstehen

Der Zugriff auf ein verzögert geladenes Objekt außerhalb des Kontexts einer geöffneten Ruhezustandssitzung führt zu dieser Ausnahme.

Es ist wichtig zu verstehen, was Session , Lazy Initialization und Proxy Object sind und wie sie im Hibernate- Framework zusammenkommen.

  • Sitzung ist ein Persistenzkontext, der eine Konversation zwischen einer Anwendung und der Datenbank darstellt
  • Lazy Loading bedeutet, dass das Objekt erst in den Sitzungskontext geladen wird, wenn im Code darauf zugegriffen wird.
  • Der Ruhezustand erstellt eine dynamische Proxy-Objekt- Unterklasse, die die Datenbank nur dann erreicht, wenn wir das Objekt zum ersten Mal verwenden.

Dieser Fehler bedeutet, dass wir versuchen, ein verzögert geladenes Objekt mithilfe eines Proxy-Objekts aus der Datenbank abzurufen, die Hibernate-Sitzung jedoch bereits geschlossen ist.

3. Beispiel für eine LazyInitializationException

Lassen Sie uns die Ausnahme in einem konkreten Szenario sehen.

Wir möchten ein einfaches Benutzerobjekt mit zugehörigen Rollen erstellen . Verwenden wir JUnit, um den LazyInitializationException- Fehler zu demonstrieren .

3.1. Utility-Klasse für den Ruhezustand

Definieren wir zunächst eine HibernateUtil- Klasse, um eine SessionFactory mit Konfiguration zu erstellen .

Wir werden die speicherinterne HSQLDB- Datenbank verwenden.

3.2. Entitäten

Hier ist unsere Benutzerentität :

@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; } 

Und die damit verbundene Rolle Einheit:

@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; }

Wie wir sehen können, besteht eine Eins-zu-Viele-Beziehung zwischen Benutzer und Rolle .

3.3. Benutzer mit Rollen erstellen

Als Nächstes erstellen wir zwei Rollenobjekte :

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

Dann erstellen wir einen Benutzer mit den folgenden Rollen:

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

Schließlich können wir eine Sitzung öffnen und die Objekte beibehalten:

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

3.4. Rollen holen

Im ersten Szenario erfahren Sie, wie Sie Benutzerrollen ordnungsgemäß abrufen:

@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(); }

Hier greifen wir auf das Objekt innerhalb der Sitzung zu, daher gibt es keinen Fehler.

3.5. Fehler beim Abrufen von Rollen

Im zweiten Szenario rufen wir eine getRoles- Methode außerhalb der Sitzung auf:

@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 diesem Fall versuchen wir, auf die Rollen zuzugreifen, nachdem die Sitzung geschlossen wurde. Infolgedessen löst der Code eine LazyInitializationException aus .

4. So vermeiden Sie den Fehler

Schauen wir uns vier verschiedene Lösungen an, um den Fehler zu beheben.

4.1. Sitzung in der oberen Ebene öffnen

Am besten öffnen Sie eine Sitzung in der Persistenzschicht, z. B. mithilfe des DAO-Musters.

Wir können die Sitzung in den oberen Ebenen öffnen, um auf sichere Weise auf die zugehörigen Objekte zuzugreifen. Zum Beispiel können wir die Sitzung in der Ansichtsebene öffnen .

Infolgedessen wird sich die Antwortzeit verlängern, was sich auf die Leistung der Anwendung auswirkt.

Diese Lösung ist ein Anti-Muster im Sinne des Separation of Concerns-Prinzips. Darüber hinaus kann es zu Verletzungen der Datenintegrität und zu lang laufenden Transaktionen kommen.

4.2. Einschalten enable_lazy_load_no_trans Immobilien

Diese Hibernate-Eigenschaft wird verwendet, um eine globale Richtlinie für das Abrufen von Objekten mit verzögertem Laden zu deklarieren.

Standardmäßig ist diese Eigenschaft false . Wenn Sie es aktivieren, wird jeder Zugriff auf eine zugeordnete Entität mit verzögertem Laden in eine neue Sitzung eingeschlossen, die in einer neuen Transaktion ausgeführt wird:

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 diesem Artikel haben wir gesehen , wie man mit dem beschäftigen org.hibernate.LazyInitializationException: konnte nicht initialisiert werden Proxy - kein Session Fehler .

Wir haben verschiedene Ansätze zusammen mit Leistungsproblemen untersucht. Es ist wichtig, eine einfache und effiziente Lösung zu verwenden, um Leistungseinbußen zu vermeiden.

Schließlich haben wir gesehen, wie der Join-Fetching-Ansatz ein guter Weg ist, um den Fehler zu vermeiden.

Wie immer ist der Code auf GitHub verfügbar.