Hibernate не может инициализировать прокси-сервер - нет сеанса

1. Обзор

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

В этом кратком руководстве мы более подробно рассмотрим основную причину ошибки и узнаем, как ее избежать.

2 Понимание ошибки

Доступ к объекту с отложенной загрузкой вне контекста открытого сеанса Hibernate приведет к этому исключению.

Важно понимать, что такое сеанс , отложенная инициализация и прокси-объект, и как они сочетаются в структуре Hibernate .

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

Эта ошибка означает, что мы пытаемся получить из базы данных объект с отложенной загрузкой, используя прокси-объект, но сеанс Hibernate уже закрыт.

3. Пример исключения LazyInitializationException

Давайте посмотрим на исключение в конкретном сценарии.

Мы хотим создать простой объект User со связанными ролями. Давайте воспользуемся JUnit, чтобы продемонстрировать ошибку LazyInitializationException .

3.1. Служебный класс Hibernate

Сначала давайте определим класс HibernateUtil для создания SessionFactory с конфигурацией.

Мы будем использовать базу данных HSQLDB в памяти .

3.2. Сущности

Вот наша сущность User :

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

И связанный объект Role :

@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 :

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.

Мы можем открыть сеанс на верхних уровнях для безопасного доступа к связанным объектам. Например, мы можем открыть сеанс в слое просмотра .

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

Это решение является антипаттерном с точки зрения принципа разделения проблем. Кроме того, это может привести к нарушениям целостности данных и длительным транзакциям.

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

Это свойство Hibernate используется для объявления глобальной политики отложенной загрузки объекта.

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

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.