Как решить "не удалось лениво инициализировать коллекцию ролей" исключение Hibernate

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

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 loop

Ответ 1

Если вы знаете, что каждый раз, когда вы извлекаете Topic, вы хотите видеть все Comment, то измените сопоставление полей для comments на:

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

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

Ответ 2

По моему опыту, у меня есть следующие методы для решения знаменитого исключения LazyInitialization:

(1) Использовать Hibernate.initialize

Hibernate.initialize(topics.getComments());

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

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

(3) Использовать OpenSessionInViewFilter

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

Ответ 3

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

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

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

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

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

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

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

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

Если вы хотите, чтобы коллекция была инициализирована ленивой, а также выполните эту работу, лучше добавить этот фрагмент кода к вашему 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 и классы контроллеров.

Ответ 4

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

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

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

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

fetch = FetchType.EAGER 

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

Hibernate.initialize(topics.getComments());

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

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

Ответ 5

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

Существует два решения.

  • Не используйте ленивую загрузку.

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

  • Используйте ленивую загрузку.

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

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

Подробнее См. мой POST.

Ответ 6

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

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

Ответ 7

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

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

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

    Сборник комментариевList = topicById.getComments();

  • Используйте "нетерпеливый" вместо "ленивый" . Теперь вы не используете "ленивый". Это не настоящее решение, если вы хотите использовать ленивый, работает как временное (очень временное) обходное решение.

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

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

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

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

@PersistenceContext(type = PersistenceContextType.EXTENDED)

Ответ 11

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

select t
from Topic t
left join fetch t.comments

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

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

Ответ 12

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

Ответ 13

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

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

Ответ 14

ваш список - ленивая загрузка, поэтому список не был загружен. звонка, чтобы попасть в список, недостаточно. используйте в Hibernate.initialize, чтобы запустить список. Если dosnt работает в элементе списка и вызывает 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 

Ответ 15

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

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

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

Ответ 16

Чтобы решить проблему в моем случае, она просто отсутствовала в этой строке

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

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

Аннотация @Transactional по методу не принималась во внимание.

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

Ответ 17

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

@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() делает другой запрос для получения продаж, потому что вы явно задали его.

Ответ 18

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

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

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

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

Ответ 19

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

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

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

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

Ответ 20

В моем случае у меня было чёрно-белое отображение A и B как

A имеет

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

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

Ответ 21

Коллекция 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-сеансы с состоянием, чтобы вы могли получать комментарии позже по требованию.

Ответ 22

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

topicById.getComments();

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

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

Ответ 23

Привет Всем, пишу довольно поздно, надеюсь, что это помогает другим, Заранее благодарим @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.

Ответ 24

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

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

Ответ 25

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

@Transactional

и

Hibernate.initialize(topicById.getComments());

Ответ 26

добавьте это в свой файл persistence.xml

<property name="hibernate.enable_lazy_load_no_trans" value="true" />

Ответ 27

i разрешено использовать List вместо Set:

private List<Categories> children = new ArrayList<Categories>();