Hibernate nie można zainicjować serwera proxy - brak sesji

1. Przegląd

Pracując z Hibernate, mogliśmy napotkać błąd, który mówi: org.hibernate.LazyInitializationException: nie można zainicjować proxy - brak sesji .

W tym krótkim samouczku przyjrzymy się bliżej głównej przyczynie błędu i dowiemy się, jak go uniknąć.

2 Zrozumienie błędu

Dostęp do obiektu ładowanego z opóźnieniem poza kontekstem otwartej sesji Hibernacji spowoduje ten wyjątek.

Ważne jest, aby zrozumieć, czym jest sesja , leniwa inicjalizacja i obiekt proxy oraz jak łączą się one w ramach Hibernate .

  • Sesja to kontekst trwałości, który reprezentuje konwersację między aplikacją a bazą danych
  • Ładowanie z opóźnieniem oznacza, że ​​obiekt nie zostanie załadowany do kontekstu sesji, dopóki nie zostanie udostępniony w kodzie.
  • Hibernate tworzy dynamiczną podklasę obiektu proxy , która trafi do bazy danych tylko wtedy, gdy użyjemy obiektu po raz pierwszy.

Ten błąd oznacza, że ​​próbujemy pobrać obiekt ładowany z opóźnieniem z bazy danych przy użyciu obiektu proxy, ale sesja Hibernate jest już zamknięta.

3. Przykład LazyInitializationException

Zobaczmy wyjątek w konkretnym scenariuszu.

Chcemy stworzyć prosty obiekt użytkownika z powiązanymi rolami. Użyjmy JUnit, aby zademonstrować błąd LazyInitializationException .

3.1. Klasa użyteczności hibernacji

Najpierw zdefiniujmy klasę HibernateUtil, aby utworzyć SessionFactory z konfiguracją.

Użyjemy bazy danych HSQLDB w pamięci .

3.2. Podmioty

Oto nasz podmiot użytkownika :

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

Oraz powiązana rola :

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

Jak widać, pomiędzy użytkownikiem a rolą istnieje relacja jeden do wielu .

3.3. Tworzenie użytkownika z rolami

Następnie utwórzmy dwa obiekty Role :

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

Następnie tworzymy Użytkownika z rolami:

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

Wreszcie możemy otworzyć sesję i utrwalić obiekty:

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

3.4. Pobieranie ról

W pierwszym scenariuszu zobaczymy, jak prawidłowo pobrać role użytkowników:

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

Tutaj uzyskujemy dostęp do obiektu wewnątrz sesji, dlatego nie ma błędu.

3.5. Błąd pobierania ról

W drugim scenariuszu wywołamy metodę getRoles poza sesją:

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

W takim przypadku próbujemy uzyskać dostęp do ról po zamknięciu sesji, w wyniku czego kod wyrzuca wyjątek LazyInitializationException .

4. Jak uniknąć błędu

Rzućmy okiem na cztery różne rozwiązania, aby przezwyciężyć błąd.

4.1. Otwarta sesja w górnej warstwie

Najlepszą praktyką jest otwieranie sesji w warstwie trwałości, na przykład przy użyciu wzorca DAO.

Możemy otworzyć sesję w wyższych warstwach, aby uzyskać dostęp do powiązanych obiektów w bezpieczny sposób. Na przykład możemy otworzyć sesję w warstwie Widok .

W rezultacie zobaczymy wydłużenie czasu odpowiedzi, co wpłynie na wydajność aplikacji.

To rozwiązanie jest anty-wzorcem w zakresie zasady separacji obaw. Ponadto może powodować naruszenia integralności danych i długotrwałe transakcje.

4.2. Włączanie właściwości enable_lazy_load_no_trans

Ta właściwość Hibernate służy do deklarowania globalnych zasad dotyczących pobierania obiektów z opóźnionym ładowaniem.

Domyślnie ta właściwość ma wartość false . Włączenie go oznacza, że ​​każdy dostęp do skojarzonej z opóźnieniem encji zostanie opakowany w nową sesję działającą w nowej transakcji:

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

W tym artykule widzieliśmy, jak radzić sobie z wyjątkiem org.hibernate.LazyInitializationException: nie można zainicjować serwera proxy - brak błędu sesji .

Zbadaliśmy różne podejścia oraz problemy z wydajnością. Ważne jest, aby użyć prostego i wydajnego rozwiązania, aby uniknąć wpływu na wydajność.

Wreszcie zobaczyliśmy, że podejście do pobierania sprzężeń jest dobrym sposobem na uniknięcie błędu.

Jak zawsze kod jest dostępny w serwisie GitHub.