Как решить проблему "не удалось лениво инициализировать коллекцию ролей" в исключении 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

35 ответов

Решение

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

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

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

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

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

Hibernate.initialize(topics.getComments());

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

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

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

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

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

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

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

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

fetch = FetchType.EAGER 

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

Hibernate.initialize(topics.getComments());

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

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

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

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

Если вы хотите инициализировать коллекцию 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 и классы контроллеров.

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

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

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

select t
from Topic t
left join fetch t.comments

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Я получил эту ошибку при втором выполнении генерации токена JWT. Не в первом.

Линия user.getUsersRole().stream().forEachOrdered((ur) -> roles.add(ur.getRoleId())); выдал ошибку.

      // MyUserDetails.java

@Service
public class MyUserDetails implements UserDetailsService {

  @Override
  public UserDetails loadUserByUsername(String email) {
    // ...

    /* ERROR
    /* org.hibernate.LazyInitializationException: failed to 
    /* lazily initialize a collection of role: 
    /* com.organizator.backend.model.User.usersRole, 
    /* could not initialize proxy - no Session */
    user.getUsersRole().stream().forEachOrdered((ur) ->
 roles.add(ur.getRoleId()));

Добавление @Transacctional на уровне класса решил это:

      // MyUserDetails.java

// ...
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional // <-- added
public class MyUserDetails implements UserDetailsService {

  @Override
  public UserDetails loadUserByUsername(String email) {
    // ...

    /* No Error */
    user.getUsersRole().stream().forEachOrdered((ur) ->
 roles.add(ur.getRoleId()));

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

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

Две вещи, которые вам нужны fetch = FetchType.LAZY.

@Transactional

а также

Hibernate.initialize(topicById.getComments());

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

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

Есть несколько решений этой проблемы с отложенной инициализацией -

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

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

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

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

@PersistenceContext(type = PersistenceContextType.EXTENDED)

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

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

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

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

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

Проблема вызвана тем, что код обращается к ленивому отношению 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;
    }

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

Это старый вопрос, но приведенная ниже информация может помочь людям, ищущим ответ на него.

Ответ @VladMihalcea полезен. Вы не должны полагаться на FetchType.EAGER , вместо этого вы должны загружать комментарии в объект, когда это необходимо.

Если вы явно не определяете свои запросы, чтобы вы могли указать join fetch, затем используя @NamedEntityGraph и @EntityGraph Вы можете переопределитьFetchType.LAZY ( @OneToManyассоциации используют LAZY по умолчанию) во время выполнения и загружают комментарии одновременно с Topic только при необходимости . Это означает, что вы ограничиваете загрузку комментариев только теми методами (запросами), которые действительно этого требуют. Граф сущности, как это определяет JPA :

Граф сущностей можно использовать с методом find или в качестве подсказки запроса для переопределения или расширения семантики FetchType.

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

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

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

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

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

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

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

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

Не лучшее решение, но для тех, кто сталкивается 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();

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

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

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

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

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

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

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

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

В моем cae у меня было отображение ч / б A и B как

А имеет

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

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

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

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

topicById.getComments();

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

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

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

Collection<Comment> commentList = this.transactionTemplate.execute
(status -> topicById.getComments());
Другие вопросы по тегам