Как устранить LazyInitializationException при использовании JPA и Hibernate

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

@JoinTable(name = "join_profilo_funzionalita", joinColumns = {@JoinColumn(name =    "profilo_id", referencedColumnName = "profilo_id")}, inverseJoinColumns = {@JoinColumn(name = "funzionalita_id", referencedColumnName = "funzionalita_id")})
//@ManyToMany(fetch=FetchType.EAGER) - no exceptions if uncommented
@ManyToMany 
private Collection<Funzionalita> funzionalitaIdCollection;

Есть ли стандартный шаблон с использованием классов JPA, чтобы избежать этой ошибки?

Фрагменты приветствуются, большое спасибо за ваше время.

Ответов (9)

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

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

  1. Вызов соответствующего геттера. После того, как поле загружено в компонент, оно остается там после закрытия сеанса.
  2. Вы можете инициализировать поле в запросе EJBQL, ищите JOIN FETCHключевое слово.
  3. Включите AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, если вы используете версию Hibernate, которая его поддерживает.

При использовании этих решений может возникнуть несколько проблем:

  1. Вызов геттеров может быть оптимизирован JIT-компилятором (иногда это занимает некоторое время).
  2. Сущности, с которыми вы пытаетесь связаться, JOIN FETCHмогут быть связаны несколькими отношениями "многие" с участием List. В этом случае результирующий запрос возвращает неоднозначные результаты, и Hibernate откажется получать ваши данные в одном запросе.
  3. Уже есть одна интересная ошибка, связанная с AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS . И их будет больше, потому что, как говорят ребята из спящего режима: Примечание: это может произойти вне транзакции и небезопасно. Используйте с осторожностью. Вы в основном сами по себе.

Лучше всего попробовать JOIN FETCH сначала. Если это не сработает, попробуйте метод получения. Если JIT-компилятор ошибается во время выполнения, присвойте результат файлу public static volatile Object .

Или прекратите использовать Hibernate ...

Hibernate 4.1.6, наконец, решает эту проблему: https://hibernate.atlassian.net/browse/HHH-7457

Вам нужно установить свойство hibernate hibernate.enable_lazy_load_no_trans = true

Вот как это сделать весной:

<bean id="entityManagerFactory"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="myDataSource"/>
    <property name="packagesToScan" value="com.mycompany.somepackage"/>
    <property name="jpaVendorAdapter" ref="hibernateVendorAdapter"/>
    <property name="jpaDialect" ref="jpaDialect"/>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.enable_lazy_load_no_trans">true</prop>
        </props>
    </property>
</bean>

Вуаля; Теперь вам не нужно беспокоиться об исключении LazyInitializationException при навигации по вашей модели предметной области за пределами спящего режима (контекст сохранения в "JPA-языке")

Обратите внимание, что вам не следует использовать hibernate.enable_lazy_load_no_trans pre Hibernate 4.1.7, так как он пропускает соединения. См. https://hibernate.onjira.com/browse/HHH-7524.

В руководствах Oracle Java указывается, что «корпоративные компоненты поддерживают транзакции, механизмы, управляющие одновременным доступом к разделяемым объектам». Итак, чтобы справиться с проблемами отложенной выборки, я создаю сессионный компонент Java без сохранения состояния, а затем получаю все подклассы, которые мне нужны, прежде чем вернуться из метода. Это позволяет избежать исключения ленивой выборки. Oracle также называет это базовым шаблоном J2EE "Session Façade". Этот образец кажется лучше, чем некоторые другие упомянутые практики.

Лучший способ устранить LazyInitializationException - использовать директиву JOIN FETCH в запросах сущностей.

Загрузка FetchType.EAGER плохо сказывается на производительности. Также есть такие антипаттерны, как:

Которые вы никогда не должны использовать, так как они либо требуют, чтобы соединение с базой данных было открыто для рендеринга пользовательского интерфейса (Open Session in View), либо соединение с базой данных требуется для каждой ленивой ассоциации, которая выбирается за пределами начального контекста постоянства ( hibernate.enable_lazy_load_no_trans ).

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

Я работаю над проектом, который направлен на решение общих проблем JPA при сопоставлении сущностей с DTO с помощью ModelMapper. Этот вопрос уже решен на проекте. Ссылка на проект: JPA Model Mapper

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

Источник: JPA Model Mapper

Поэтому в проекте мы имеем дело с LazyInitializationException, устанавливая null для всех незагруженных сущностей. Примеры ниже показывают, как это работает.

Переназначение значения параметра сущности для всех незагруженных сущностей:

TypedQuery<SystemEntity> query =
        em.createQuery("select s from SystemEntity s where s.id = 1",  SystemEntity.class);

SystemEntity system = query.getSingleResult();
return new JpaModelMapper(em).mapEntity(system, SystemEntity.class);

Переназначение сущности в значение DTO, равное нулю для всех незагруженных сущностей:

TypedQuery<SystemEntity> query =
        em.createQuery("select s from SystemEntity s where s.id = 1",  SystemEntity.class);

SystemEntity system = query.getSingleResult();
return new JpaModelMapper(em).mapEntity(system, SystemDTO.class);

Для получения дополнительной информации см. JPA Model Mapper.

OpenSessionInView - один из способов решения этой проблемы. Некоторая информация здесь:

http://www.hibernate.org/43.html

При реализации этого шаблона следует проявлять осторожность и понимать последствия. Каждый раз, когда вы перемещаетесь по ленивой ассоциации в представлении, она запускает другой запрос SQL для загрузки данных. Если ваши варианты использования таковы, что количество и размер этих SQL-запросов невелик, это может не иметь значения. Убедитесь, что вы как минимум изменили настройки ведения журнала, чтобы вы могли видеть, какие запросы Hibernate «волшебным образом» выполняет в фоновом режиме для загрузки данных.

Также подумайте о том, какое приложение вы пишете. Если вы не имеете дело с удаленным взаимодействием (без веб-сервисов, без веб-клиента на основе AJAX), тогда OSIV может работать очень хорошо. Однако, если удаленный сериализатор начнет обход всего графа объекта, он, скорее всего, вызовет невероятное количество SQL-запросов и нанесет вред вашей БД и серверу приложений.

LazyInitializationException означает, что вы вызываете коллекцию после закрытия сеанса гибернации или после того, как объект был отсоединен от сеанса.

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