Хибернацията не можа да инициализира прокси - няма сесия

1. Общ преглед

Работейки с Hibernate, може да сме срещнали грешка, която казва: org.hibernate.LazyInitializationException: не може да инициализира прокси - няма сесия .

В този бърз урок ще разгледаме по-отблизо първопричината за грешката и ще научим как да я избегнем.

2 Разбиране на грешката

Достъпът до лениво натоварен обект извън контекста на отворена сесия на хибернация ще доведе до това изключение.

Важно е да разберете какво е сесия , мързелива инициализация и прокси обект и как те се обединяват в рамките на хибернацията .

  • Сесията е контекст на постоянство, който представлява разговор между приложение и базата данни
  • Мързеливо зареждане означава, че обектът няма да бъде зареден в контекста на сесията, докато не бъде достъпен в код.
  • Hibernate създава динамичен подклас Proxy Object, който ще удари базата данни само когато за първи път използваме обекта.

Тази грешка означава, че се опитваме да извлечем лениво натоварен обект от базата данни, като използваме прокси обект, но сесията Hibernate вече е затворена.

3. Пример за LazyInitializationException

Нека видим изключението в конкретен сценарий.

Искаме да създадем прост потребителски обект със свързани роли. Нека използваме JUnit, за да демонстрираме грешката LazyInitializationException .

3.1. Hibernate Utility Class

Първо, нека дефинираме клас HibernateUtil, за да създадем SessionFactory с конфигурация.

Ще използваме базата данни HSQLDB в паметта .

3.2. Субекти

Ето нашия потребителски обект:

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

И свързаният обект на ролята :

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

Както виждаме, съществува връзка „един към много“ между потребителя и ролята .

3.3. Създаване на потребител с роли

След това нека създадем два Ролеви обекта:

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

След това създаваме потребител с ролите:

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

И накрая, можем да отворим сесия и да запазим обектите:

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

3.4. Извличане на роли

В първия сценарий ще видим как да извлечем потребителските роли по правилен начин:

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

Тук имаме достъп до обекта вътре в сесията, следователно няма грешка.

3.5. Неуспех при извличането на ролите

Във втория сценарий ще извикаме метод getRoles извън сесията:

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

В този случай се опитваме да осъществим достъп до ролите след затварянето на сесията и в резултат на това кодът изхвърля LazyInitializationException .

4. Как да избегнем грешката

Нека да разгледаме четири различни решения за преодоляване на грешката.

4.1. Отворете сесията в горния слой

Най-добрата практика е да отворите сесия в персистиращия слой, например с помощта на DAO Pattern.

Можем да отворим сесията в горните слоеве за безопасен достъп до свързаните обекти. Например можем да отворим сесията в слоя View .

В резултат на това ще видим увеличаване на времето за реакция, което ще повлияе на производителността на приложението.

Това решение е анти-модел по отношение на принципа за разделяне на проблемите. Освен това може да причини нарушения на целостта на данните и дълготрайни транзакции.

4.2. Включване на enable_lazy_load_no_trans имота

Това свойство Hibernate се използва за деклариране на глобална политика за мързеливо заредено извличане на обекти.

По подразбиране това свойство е невярно . Включването му означава, че всеки достъп до свързан мързеливо натоварен обект ще бъде обвит в нова сесия, изпълнявана в нова транзакция:

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

В тази статия видяхме как да се справим с org.hibernate.LazyInitializationException: не можа да инициализира прокси - няма грешка в сесията .

Проучихме различни подходи заедно с проблеми с производителността. Важно е да използвате просто и ефективно решение, за да избегнете засягане на производителността.

И накрая, видяхме как подходът за извличане на съединения е добър начин да се избегне грешката.

Както винаги, кодът е достъпен в GitHub.