Рубрики
Без рубрики

Hibernate не удалось инициализировать прокси – сервер-нет сеанса

Узнайте, как обращаться с org.hibernate.Исключение LazyInitializationException : не удалось инициализировать прокси – сервер-нет сеанса в Java

Автор оригинала: baeldung.

1. Обзор

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

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

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

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

Важно понять что такое Сессия , Ленивая инициализация, | и Прокси-объект и как они объединяются в Спящий режим фреймворк.

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

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

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

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

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

3.1. Служебный класс Гибернации

Во-первых, давайте определим 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 .

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

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

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

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

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

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

Использование этого свойства, чтобы избежать LazyInitializationException ошибка не рекомендуется, так как это замедлит производительность нашего приложения. Это потому, что мы в конечном итоге столкнемся с проблемой n + 1 . Проще говоря, это означает один выбор для Пользователя , а не дополнительные варианты выбора для выбора ролей каждого пользователя.

Этот подход неэффективен и также считается анти-шаблоном.

4.3. Использование Тип выборки.НЕТЕРПЕЛИВАЯ Стратегия

Мы можем использовать эту стратегию вместе с аннотацией @OneToMany , например:

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

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

Таким образом, гораздо проще объявить тип “НЕТЕРПЕЛИВАЯ выборка” вместо явной выборки коллекции для большинства различных бизнес-потоков.

4.4. Использование Выборки Соединений

Мы можем использовать директиву JOIN FETCH в JPQL для извлечения связанной коллекции по требованию, например:

SELECT u FROM User u JOIN FETCH u.roles

Или мы можем использовать API критериев гибернации:

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

Здесь мы указываем связанную коллекцию, которая должна быть извлечена из базы данных вместе с объектом User в одном и том же цикле. Использование этого запроса повышает эффективность итерации, поскольку устраняет необходимость в извлечении связанных объектов отдельно.

Это наиболее эффективное и мелкозернистое решение, позволяющее избежать Исключение LazyInitializationException ошибка.

5. Заключение

В этой статье мы рассмотрели, как работать с org.hibernate.Исключение LazyInitializationException: не удалось инициализировать прокси – сервер-нет сеанса ошибка .

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

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

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