Hibernate «не удалось лениво инициализировать коллекцию» ошибка времени выполнения - PullRequest
0 голосов
/ 15 января 2020

Этот вопрос задавался много раз, но я все еще не могу найти решение для моего варианта использования:

  1. У меня есть приложение Struts2, использующее Hibernate 5.x.

  2. Приложение представляет собой приложение «Контакты». Он состоит из двух объектов: «Контакт», который может иметь ноль или более «Заметок».

  3. Вот как я получаю контакты:

    @Override
    public List<Contact> getContacts() {
    //Note: Hibernate 5++ supports Java try-with-resource blocks
    try (Session session = HibernateUtil.openSession()) {
        List<Contact> contacts = session.createQuery("FROM Contact").list();
        return contacts;
        ...
    
  4. Отлично работает. Пока я не попробую что-то вроде этого:

    ObjectMapper mapper = new ObjectMapper();
    jsonString = mapper.writeValueAsString(contacts);
    

ОШИБКА:

16:05:16.174 [http-nio-8080-exec-2] DEBUG org.apache.struts2.dispatcher.Dispatcher - Dispatcher serviceAction failed
com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.example.contactsapp.models.Contact.notes, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.example.contactsapp.models.Contact["notes"])
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:394) ~[jackson-databind-2.10.0.jar:2.10.0]
    ...
    at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:4094) ~[jackson-databind-2.10.0.jar:2.10.0]
    at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3404) ~[jackson-databind-2.10.0.jar:2.10.0]
    at com.example.contactsapp.actions.ContactsAction.getContacts(ContactsAction.java:36) ~[classes/:?]
    ...

ВОЗМОЖНЫЕ РЕШЕНИЯ

  1. Я НЕ хочу изменить на FetchType.Eager.

  2. Поскольку я не использую Spring, я не могу используйте @Transactional или OpenSessionInView. Но я бы с удовольствием, если бы были эквиваленты только для Hibernate.

  3. Это то, что я пробовал (в основном на Как решить «не удалось лениво инициализировать» коллекция ролей »Hibernate исключение ):

    @Override
    public List<Contact> getContactsFetchAll() {
      //Note: Hibernate 5++ supports Java try-with-resource blocks
      try (Session session = HibernateUtil.openSession()) {
        // Jackson mapper.writeValueAsString() => "failed to lazily initialize a collection" 
        // List<Contact> contacts = session.createQuery("FROM Contact").list();
    
        // Plan A: Causes same "failed to lazily initialize a collection" runtime error
        // List<Contact> contacts = session.createQuery("FROM Contact").list();
        // Hibernate.initialize(contacts);
    
        // Plan B: Still no-go: returns [] empty set
        // List<Contact> contacts = session.createQuery("SELECT c FROM Contact c JOIN FETCH c.notes n").list();
    
        // Plan C: Same: "failed to lazily initialize a collection of role..."
        // Query query = session.createQuery("FROM Contact");
        // Hibernate.initialize(query);
        // List<Contact> contacts = query.list(); 
    
        // Plan D: Same: "ERROR: failed to lazily initialize a collection of role"
        // List<Contact> contacts = session.createQuery("FROM Contact").list();
        // for (Contact c : contacts) {
        //    Set<Note> n = c.getNotes();
        // }
           return contacts;
        }
     }
    

Q: Есть предложения?

Q: Вам нужна дополнительная информация?


Я провел немного больше исследований.

По-видимому, в этом конкретном сценарии Hibernate вообще не выполняет "соединение".

Вместо этого:

  1. Делает "выбор", чтобы получить родителя.
  2. Если вы попытаетесь прочитать что-либо из какого-либо из детей, тогда он ДРУГОЙ"выберет", чтобы получить детей. Один «выбор» для родителя, второй для детей.
  3. Если объект настроен на «активную выборку», он всегда все выбирает заранее.
  4. Для "отложенной выборки" вы ДОЛЖНЫ пытаться прочитать дочерние элементы (таким образом, вызывая второй выбор) перед закрытием сеанса. В противном случае, если вы попытаетесь прочитать дочерние данные после закрытия сеанса, вы получите «не удалось лениво инициализировать коллекцию».
  5. В этом сценарии левое соединение не работает: набор результатов, возвращаемый базой данных просто не соответствует сущности. Чтобы это работало, мне нужно было прочитать строку набора результатов за раз и построить объект вручную.
  6. Я все еще ищу способ заставить "Join Fetch" работать в моем сценарий ...

Ответы [ 2 ]

1 голос
/ 15 января 2020

В случае, если у вас есть сопоставление OneToMany с отложенной инициализацией в конфигурации сущностей, и в некоторых случаях вы хотите охотно получать коллекцию. Я думаю, что вы можете использовать @NamedQuery для получения списка в одном запросе (в вашем случае Left Join).

0 голосов
/ 17 января 2020

Я работал с этой темой еще до того, как опубликовал свой вопрос:

Как решить проблему «не удалось лениво инициализировать коллекцию ролей», исключение гибернации

Проблема вызвана доступом к атрибуту с закрытым сеансом гибернации. У вас нет транзакции гибернации в контроллере.

Возможные решения:

  • Сделайте все эти логи c в уровень обслуживания , (с @Transactional), не в контроллере. Там должно быть правильное место, чтобы сделать это, это часть логики c приложения, а не в контроллере (в данном случае, интерфейс для загрузки модели). Все операции на уровне сервиса должны быть транзакционными ...

  • Использовать 'eager' вместо 'lazy' . Теперь вы не используете 'ленивый' .. это не реальное решение, если вы хотите использовать ленивый, работает как временный (очень временный) обходной путь.

  • использование @Transactional в контроллере . Это не должно использоваться здесь, вы смешиваете сервисный уровень с представлением, это не очень хороший дизайн.

  • используйте OpenSessionInViewFilter , много недостатков, возможная нестабильность.

К сожалению, @Transactional и OpenSessionInView не были доступны в моем сценарии. FetchType.EAGER не вариант. И «делать все внутри сеанса» было именно той проблемой, которую я пытался решить.

Еще один отличный ответ от той же темы :

От моего опыт, у меня есть следующие методы для решения знаменитой LazyInitializationException:

  • Использование Hibernate.initialize

    Hibernate.initialize(topics.getComments());

  • Использовать JOIN FETCH

Вы можете использовать синтаксис JOIN FETCH в вашем JPQL для явного извлечения дочерней коллекции. Это похоже на извлечение EAGER.

  • Использование OpenSessionInViewFilter

Что я выучил:

  • List<Contact> contacts = session.createQuery("FROM Contact").list();: возвращает только «родительскую» (контактную) запись, ни одна из дочерних («заметок») записей.

    ВОЗМОЖНОЕ РЕШЕНИЕ: явно прочитайте каждую из дочерних заметок , пока сеанс все еще активен . Это вызывает второй «выбор» и извлекает дочерние данные.

  • List<Contact> contacts = session.createQuery("SELECT c FROM Contact c INNER JOIN FETCH c.notes").list();: не возвращает контактов с 0 дочерними заметками.

    ВОЗМОЖНОЕ РЕШЕНИЕ: Изменить код, гарантирующий, что у каждого контакта есть хотя бы одна заметка (путем автоматического создания заметки при создании контакта). «Join join» делает только один SQL звонок.

В любом случае - у меня все получилось.

...