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


363

У меня есть эта проблема:

org.hibernate.LazyInitializationException: не удалось лениво инициализировать коллекцию ролей: mvc3.model.Topic.comments, ни один сеанс или сеанс не был закрыт

Вот модель:

@Entity
@Table(name = "T_TOPIC")
public class Topic {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;

    @ManyToOne
    @JoinColumn(name="USER_ID")
    private User author;

    @Enumerated(EnumType.STRING)    
    private Tag topicTag;

    private String name;
    private String text;

    @OneToMany(mappedBy = "topic", cascade = CascadeType.ALL)
    private Collection<Comment> comments = new LinkedHashSet<Comment>();

    ...

    public Collection<Comment> getComments() {
           return comments;
    }

}

Контроллер, который вызывает модель, выглядит следующим образом:

@Controller
@RequestMapping(value = "/topic")
public class TopicController {

    @Autowired
    private TopicService service;

    private static final Logger logger = LoggerFactory.getLogger(TopicController.class);


    @RequestMapping(value = "/details/{topicId}", method = RequestMethod.GET)
    public ModelAndView details(@PathVariable(value="topicId") int id)
    {

            Topic topicById = service.findTopicByID(id);
            Collection<Comment> commentList = topicById.getComments();

            Hashtable modelData = new Hashtable();
            modelData.put("topic", topicById);
            modelData.put("commentList", commentList);

            return new ModelAndView("/topic/details", modelData);

     }

}

Страница jsp выглядит следующим образом:

<%@page import="com.epam.mvc3.helpers.Utils"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
      <title>View Topic</title>
</head>
<body>

<ul>
<c:forEach items="${commentList}" var="item">
<jsp:useBean id="item" type="mvc3.model.Comment"/>
<li>${item.getText()}</li>

</c:forEach>
</ul>
</body>
</html>

Исключение повышается при просмотре jsp. В строке с циклом c: forEach

Ответы:


214

Если вы знаете, что вам нужно будет видеть все Comments каждый раз, когда вы получаете a, Topicизмените ваше отображение поля commentsна:

@OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();

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


35
Извините, но я бы хотел использовать lazy-load. Итак, я изменил тип LinkedHashSet на PersistentList. Исключение по-прежнему происходит
Евгений

242
Это можно использовать как обходной путь, но не как реальное решение проблемы. Что делать, если нам нужно лениво доставать?
Dkyc

14
но в случае, если мы хотим, чтобы ленивый, то это решение не будет работать, и в большинстве случаев мы хотим только ленивый.
Прашант Такре

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

13
@darrengorman Когда я запустил JPA, я разместил вопрос, похожий на вопрос ОП. Я получил тот же ответ, что и вы. Достаточно скоро, когда я провел какой-нибудь тест с сотнями тысяч строк, угадайте, что случилось? Я думаю, что это вводит в заблуждение, потому что это дает слишком простой ответ на проблему, с которой в основном сталкиваются новички, и достаточно скоро они загрузят всю свою базу данных в память, если они не будут осторожны (и не будут, потому что они не будут знать об этом) :).
Ced

182

Исходя из моего опыта, у меня есть следующие методы для решения известной исключительной ситуации LazyInitializationException:

(1) Используйте Hibernate.initialize

Hibernate.initialize(topics.getComments());

(2) Используйте JOIN FETCH

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

(3) Используйте OpenSessionInViewFilter

LazyInitializationException часто возникают в слое представления. Если вы используете Spring Framework, вы можете использовать OpenSessionInViewFilter. Тем не менее, я не предлагаю вам сделать это. Это может привести к проблемам с производительностью, если не использовать правильно.


5
(1) работал на меня отлично. Мой случай: Hibernate.initialize (registry.getVehicle (). GetOwner (). GetPerson (). GetAddress ());
Леонель Санчес да Силва

6
Похоже, что Hibernate.initialize не работает с EntityManager
marionmaiden

8
Это должен быть правильный ответ. Например, в моем проекте на работе мы явно не должны использовать выборку EAGER. Это вызывает проблемы в этой конкретной системе.
Стив Уотерс

Кажется привлекательным, но отсутствие документации для реализации в другом случае ... Не могли бы вы предоставить еще несколько ссылок или объяснений о том, как реализовать это решение?
Пипо

58

Я знаю, что это старый вопрос, но я хочу помочь. Вы можете поместить транзакционную аннотацию на нужный вам сервисный метод, в этом случае findTopicByID (id) должен иметь

@Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)

больше информации об этой аннотации можно найти здесь

О других решениях:

fetch = FetchType.EAGER 

не является хорошей практикой, его следует использовать ТОЛЬКО в случае необходимости.

Hibernate.initialize(topics.getComments());

Инициализатор hibernate связывает ваши классы с технологией hibernate. Если вы стремитесь быть гибкими, это не лучший способ.

Надеюсь, поможет


3
Аннотация @Transactional работала для меня, но учтите, что Propagation.REQUIRED используется по умолчанию, по крайней мере, в Spring Boot 1.4.2 (Spring 4.3).
ben3000

4
Да, это так, но я подумал, что было бы
полезно

Разве @Transactionalвесна не только вещь?
Кампа

@ Кампа, да, это так. Если вы хотите обработать это вручную, вы должны поместить свою бизнес-логику в транзакцию, полученную от менеджера сущностей
sarbuLopex

54

Происхождение вашей проблемы:

По умолчанию hibernate лениво загружает коллекции (отношения), что означает, что всякий раз, когда вы используете collectionв своем коде (здесь commentsполе в Topicклассе), hibernate получает это из базы данных, теперь проблема в том, что вы получаете коллекцию в своем контроллере (где сеанс JPA закрыт). Это строка кода, которая вызывает исключение (где вы загружаете commentsколлекцию):

    Collection<Comment> commentList = topicById.getComments();

Вы получаете коллекцию "comments" (topic.getComments ()) в вашем контроллере (где JPA sessionэто закончилось), и это вызывает исключение. Также, если у вас есть commentsколлекция в вашем JSP-файле, как это (вместо того, чтобы получить ее в вашем контроллере):

<c:forEach items="topic.comments" var="item">
//some code
</c:forEach>

У вас все равно будет то же исключение по той же причине.

Решение проблемы:

Поскольку у вас может быть только две коллекции с FetchType.Eager(с нетерпением выбранной коллекцией) в классе Entity, и поскольку ленивая загрузка более эффективна, чем с нетерпением загрузка, я думаю, что этот способ решения вашей проблемы лучше, чем просто изменение на FetchTypeeager:

Если вы хотите инициализировать коллекцию Lazy, а также сделать эту работу, лучше добавить этот фрагмент кода в ваш web.xml:

<filter>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Этот код делает то, что он увеличивает длину вашего JPA sessionили, как сказано в документации, он используется "to allow for lazy loading in web views despite the original transactions already being completed."так, чтобы сессия JPA была открыта немного дольше, и из-за этого вы можете лениво загружать коллекции в ваши файлы jsp и классы контроллеров. ,


7
Почему сессия JPS закрыта? Как сделать так, чтобы оно не закрывалось? Как выполнить ленивый сбор?
Димс

1
Что определяет ограничение в две коллекции FetchType.Eager на одну сущность?
chrisinmtown

В Spring Boot вы можете добавить «spring.jpa.open-in-view = true» в «application.properties»
Аскар,

28

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

Есть два решения.

  1. Не используйте ленивый груз.

    Установить lazy=falseв XML или Установить @OneToMany(fetch = FetchType.EAGER)в аннотации.

  2. Используйте ленивый груз.

    Установить lazy=trueв XML или Установить @OneToMany(fetch = FetchType.LAZY)в аннотации.

    и добавить OpenSessionInViewFilter filterв свойweb.xml

Подробнее см. Мой пост .


1
... и все же оба решения не являются хорошими. Предложить использование EAGER может создать огромные проблемы. Использование OpenSessionInViewFilter является анти-паттерном.
Рафаэль

27
@Controller
@RequestMapping(value = "/topic")
@Transactional

я решаю эту проблему, добавляя @Transactional, я думаю, что это может сделать сессию открытой


Почему это получило отрицательный голос? Добавление транзакции к операции продлевает сеанс
Тудор

1
Плохая практика - рекламировать @Transactional для контроллера.
Рафаэль

@Rafael Почему это плохая практика?
Амр Эллафи

@AmrEllafy -> Вот хорошее объяснение: stackoverflow.com/a/18498834/1261162
Рафаэль

22

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

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

  1. Выполните всю эту логику на уровне обслуживания (с @Transactional), а не в контроллере. Должно быть правильное место для этого, это часть логики приложения, а не контроллера (в данном случае интерфейс для загрузки модели). Все операции на уровне сервиса должны быть транзакционными. т.е.: переместите эту строку в метод TopicService.findTopicByID:

    Коллекция commentList = topicById.getComments ();

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

  3. используйте @Transactional в контроллере . Это не должно использоваться здесь, вы смешиваете сервисный слой с презентацией, это не очень хороший дизайн.
  4. использовать OpenSessionInViewFilter , сообщается о многих недостатках, возможна нестабильность.

В общем, лучшим решением является 1.


2
Выборочный тип Eager предполагал, что в hibernate будут извлечены все данные в первом запросе, а не во всех местах, где он находится правильно
Жасулан Бердибеков

Вы должны ПОДТВЕРДИТЬ, что ЛУЧШЕЕ РЕШЕНИЕ 1 ... на самом деле это ТОЛЬКО ХОРОШЕЕ решение, так как все остальные являются анти-шаблонами!
Рафаэль

19

Для отложенной загрузки коллекции должен быть активный сеанс. В веб-приложении есть два способа сделать это. Вы можете использовать шаблон Open Session In View , где вы используете перехватчик, чтобы открыть сеанс в начале запроса и закрыть его в конце. Существует риск того, что вам потребуется тщательная обработка исключений, иначе вы можете связать все свои сеансы, и ваше приложение может зависнуть.

Другой способ справиться с этим - собрать все данные, которые вам нужны в вашем контроллере, закрыть сеанс, а затем вставить данные в вашу модель. Я лично предпочитаю такой подход, так как он кажется немного ближе к духу паттерна MVC. Также, если вы получаете ошибку из базы данных таким образом, вы можете справиться с ней намного лучше, чем если бы это происходило в вашем средстве визуализации. Ваш друг в этом сценарии - Hibernate.initialize (myTopic.getComments ()). Вам также придется заново присоединить объект к сеансу, поскольку вы создаете новую транзакцию с каждым запросом. Для этого используйте session.lock (myTopic, LockMode.NONE).


15

Как я объяснил в этой статье , лучший способ справиться с ним LazyInitializationException- получить его по запросу, например:

select t
from Topic t
left join fetch t.comments

Вы должны ВСЕГДА избегать следующих анти-паттернов:

Поэтому убедитесь, что ваши FetchType.LAZYассоциации инициализируются во время запроса или в исходной @Transactionalобласти, используемой Hibernate.initializeдля вторичных коллекций.


1
Влад, есть ли у вас какие-либо предложения по работе с лениво-инициализированной коллекцией в сущности, полученной с помощью метода findById (), созданного Spring-репозиторием? Я не пишу запрос, и транзакция находится за пределами моего кода.
chrisinmtown

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

Не могли бы вы уточнить, что вы имеете в виду под «в рамках первоначальной области действия @Transactional». Мне не ясно, как я, кажется, получаю эту ошибку во время открытого сеанса (но не правильного?)
Michiel Haisma

Находясь внутри области действия o самый лучший транзакционный сервисный метод, также известный как шлюз транзакций. Проверьте TrassctionInterceptorв трассировке стека, и это один.
Влад Михальча

Один из лучших ответов на сегодняшний день ... это следует пометить как правильный. Кстати ... если предположить, что OSIV - это антишаблон, как это возможно, что по умолчанию включено в последних версиях весенней загрузки? ... может, не так уж и плохо?
Рафаэль

10

Если вы пытаетесь установить связь между сущностью и коллекцией или списком Java-объектов (например, типа Long), она бы хотела что-то вроде этого:

@ElementCollection(fetch = FetchType.EAGER)
    public List<Long> ids;

1
во многих случаях вы действительно не хотите этого делать. Вы теряете все преимущества ленивой загрузки здесь
kiedysktos

Использование EAGER не является профессиональным решением.
Рафаэль

9

Одним из лучших решений является добавление следующего в файл application.properties: spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true


1
Можете ли вы рассказать оператору, что он делает, какие-либо побочные эффекты, влияние на производительность?
PeS

3
В конце отложенной загрузки новый сеанс разветвляется каждый раз, когда ассоциативно загружается ассоциация, следовательно, большее количество соединений разветвляется и создает небольшую нагрузку на пул соединений. Если у вас есть ограничение на количество подключений, это свойство может быть неправильным для использования.
sreekmatta

2
для некоторых, это рассматривается как анти-паттерна vladmihalcea.com/...
Ури Лоя

7

Я обнаружил, что объявление @PersistenceContextas EXTENDEDтакже решает эту проблему:

@PersistenceContext(type = PersistenceContextType.EXTENDED)

1
Привет, будь осторожен с такими изменениями. Создание контекста персистентности в области транзакций является ленивым, что было целью OP. Поэтому вопрос в том, хотите ли вы быть лицом без гражданства или нет. Этот параметр зависит от назначения системы и не должен изменяться слишком ... охотно. Если вы понимаете, о чем я. Читайте здесь stackoverflow.com/questions/2547817/…
kiedysktos

Dangerous. Это не правильный ответ. Есть и другие, гораздо более точные и безопасные.
Рафаэль

5

это была проблема, с которой я недавно столкнулся, которую я решил с помощью

<f:attribute name="collectionType" value="java.util.ArrayList" />

Более подробное описание здесь и это спасло мой день.


5

Ваш список загружается медленно, поэтому список не был загружен. звонка для попадания в список недостаточно. используйте в Hibernate.initialize для инициации списка. Если работа не выполняется, запустите элемент списка и вызовите Hibernate.initialize для каждого элемента. это должно быть до того, как вы вернетесь из области транзакции. посмотрите на этот пост
ищи -

Node n = // .. get the node
Hibernate.initialize(n); // initializes 'parent' similar to getParent.
Hibernate.initialize(n.getChildren()); // pass the lazy collection into the session 

4

Для решения проблемы в моем случае просто не хватало этой строки

<tx:annotation-driven transaction-manager="myTxManager" />

в файле контекста приложения.

@TransactionalАннотация по методе не была принята во внимание.

Надеюсь, что ответ поможет кому-то


4

@ Транзакционная аннотация на контроллере отсутствует

@Controller
@RequestMapping("/")
@Transactional
public class UserController {
}

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

Транзакционная аннотация не отсутствует. Контроллер не должен иметь такой аннотации. Эти аннотации должны быть на уровне сервиса.
Рафаэль

4

Используя @Transactionalаннотацию hibernate , если вы получаете объект из базы данных с лениво извлеченными атрибутами, вы можете просто получить их, извлекая эти атрибуты следующим образом:

@Transactional
public void checkTicketSalePresence(UUID ticketUuid, UUID saleUuid) {
        Optional<Ticket> savedTicketOpt = ticketRepository.findById(ticketUuid);
        savedTicketOpt.ifPresent(ticket -> {
            Optional<Sale> saleOpt = ticket.getSales().stream().filter(sale -> sale.getUuid() == saleUuid).findFirst();
            assertThat(saleOpt).isPresent();
        });
}

Здесь, в транзакции, управляемой прокси-сервером Hibernate, факт вызова вызывает ticket.getSales()другой запрос для получения продаж, потому что вы явно задали его.


4

Две вещи, которые вы должны иметь fetch = FetchType.LAZY.

@Transactional

а также

Hibernate.initialize(topicById.getComments());

2

Для тех, кто работает с критериями , я обнаружил, что

criteria.setFetchMode("lazily_fetched_member", FetchMode.EAGER);

сделал все, что мне было нужно.

Режим начальной выборки для коллекций установлен на FetchMode.LAZY, чтобы обеспечить производительность, но когда мне нужны данные, я просто добавляю эту строку и наслаждаюсь полностью заполненными объектами.


2

В моем случае следующий код был проблемой:

entityManager.detach(topicById);
topicById.getComments() // exception thrown

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

Hibernate.initialize(topicById.getComments());
entityManager.detach(topicById);
topicById.getComments() // works like a charm

1

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

topicById.getComments();

Выше будет загружать commentList, только если ваш сеанс гибернации активен, который, я думаю, вы закрыли в своем сервисе.

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


2
Да, это постановка проблемы. Вы также должны предоставить ответ вAnswer
Sarz

1

Коллекция commentsв вашем классе модели Topicзагружается лениво, что является поведением по умолчанию, если вы не аннотируете ее fetch = FetchType.EAGERспециально.

Скорее всего, ваш findTopicByIDсервис использует сеанс Hibernate без сохранения состояния. Сеанс без сохранения состояния не имеет кеша первого уровня, т. Е. Отсутствует постоянный контекст. Позже, когда вы попытаетесь выполнить итерацию comments, Hibernate выдаст исключение.

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: mvc3.model.Topic.comments, no session or session was closed

Решение может быть:

  1. Аннотировать commentsсfetch = FetchType.EAGER

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)   
    private Collection<Comment> comments = new LinkedHashSet<Comment>();
  2. Если вы все еще хотите, чтобы комментарии загружались лениво, используйте сеансы с сохранением состояния в Hibernate , чтобы вы могли получать комментарии позже по требованию.


1

В моем случае у меня было отображение ч / б AиB вроде

A имеет

@OneToMany(mappedBy = "a", cascade = CascadeType.ALL)
Set<B> bs;

в DAOслое, метод должен быть аннотирован, @Transactionalесли вы не аннотировали отображение с помощью Fetch Type - Eager


1

Не лучшее решение, но для тех, кто LazyInitializationExceptionособенно сталкивается, в Serializationэтом поможет. Здесь вы проверите лениво инициализированные свойства и их настройки null. Для этого создайте класс ниже

public class RepositoryUtil {
    public static final boolean isCollectionInitialized(Collection<?> collection) {
        if (collection instanceof PersistentCollection)
            return ((PersistentCollection) collection).wasInitialized();
        else 
            return true;
    }   
}

Внутри вашего класса Entity, который имеет лениво инициализированные свойства, добавьте метод, как показано ниже. Добавьте все ваши свойства ленивой загрузки внутри этого метода.

public void checkLazyIntialzation() {
    if (!RepositoryUtil.isCollectionInitialized(yourlazyproperty)) {
        yourlazyproperty= null;
    }

Вызовите этот checkLazyIntialzation()метод после на всех местах, где вы загружаете данные.

 YourEntity obj= entityManager.find(YourEntity.class,1L);
  obj.checkLazyIntialzation();

0

Привет Всем, довольно поздняя публикация, надеюсь, что это поможет другим, Заранее благодарю @GMK за этот пост Hibernate.initialize (object)

когда ленивый = "правда"

Set<myObject> set=null;
hibernateSession.open
set=hibernateSession.getMyObjects();
hibernateSession.close();

Теперь, если я получаю доступ к 'set' после закрытия сессии, он генерирует исключение.

Мое решение:

Set<myObject> set=new HashSet<myObject>();
hibernateSession.open
set.addAll(hibernateSession.getMyObjects());
hibernateSession.close();

Теперь я могу получить доступ к «set» даже после закрытия Hibernate Session.


0

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

Collection<Comment> commentList = this.transactionTemplate.execute
(status -> topicById.getComments());

0

Проблема возникает из-за того, что код обращается к отложенному отношению JPA, когда «соединение» с базой данных закрыто ( постоянный контекст - это правильное имя в терминах Hibernate / JPA).

Простым способом решения этой проблемы в Spring Boot является определение уровня обслуживания и использование @Transactionalаннотации. Эта аннотация в методе создает транзакцию, которая распространяется на уровень хранилища и сохраняет открытый контекст постоянства до завершения метода. Если вы обращаетесь к коллекции внутри транзакционного метода, Hibernate / JPA будет извлекать данные из базы данных.

В вашем случае вам просто нужно аннотировать с @Transactionalпомощью метода findTopicByID(id)в вашем TopicServiceи вызвать выборку коллекции в этом методе (например, задавая его размер):

    @Transactional(readOnly = true)
    public Topic findTopicById(Long id) {
        Topic topic = TopicRepository.findById(id).orElse(null);
        topic.getComments().size();
        return topic;
    }

0

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

На мой взгляд, лучший подход - использовать DTO, а не сущность. В этом случае вы можете явно указать поля, которые хотите использовать. Как обычно, этого достаточно. Не нужно беспокоиться, что-то вроде ДжексонаObjectMapper или hashCodeсгенерированное Lombok вызовет ваши методы неявно.

Для некоторых конкретных случаев вы можете использовать @EntityGrpaphаннотации, которые позволяют eagerзагружать данные даже в том случае, если они есть fetchType=lazyв вашей сущности.


0

Есть многократное решение для этой проблемы Ленивой Инициализации -

1) Измените тип извлечения ассоциации с LAZY на EAGER, но это не очень хорошая практика, потому что это ухудшит производительность.

2) Используйте FetchType.LAZY для связанного объекта, а также используйте аннотацию Transactional в вашем методе уровня службы, чтобы сеанс оставался открытым, а когда вы будете вызывать topicById.getComments (), будет загружен дочерний объект (комментарии).

3) Также попробуйте использовать объект DTO вместо сущности на уровне контроллера. В вашем случае сессия закрыта на уровне контроллера. ТАК лучше конвертировать сущность в DTO на уровне сервиса.


-11

я решил использовать список вместо Set:

private List<Categories> children = new ArrayList<Categories>();
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.