org.hibernate.LazyInitializationException на com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel

Несмотря на FetchType.EAGER а также JOIN FETCHЯ получаю LazyInitalizationException при добавлении некоторых объектов в @ManyToMany сбор через JSF UISelectMany компонент, такой как в моем случае <p:selectManyMenu>,

@Entity IdentUser, с FetchType.EAGER:

@Column(name = "EMPLOYERS")
@ManyToMany(fetch = FetchType.EAGER, cascade= CascadeType.ALL)
@JoinTable(name = "USER_COMPANY", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = { @JoinColumn(name = "COMPANY_ID") })
private Set<Company> employers = new HashSet<Company>();

@Entity Company, с FetchType.EAGER:

@ManyToMany(mappedBy="employers", fetch=FetchType.EAGER)
private List<IdentUser> employee;

JPQL, с JOIN FETCH:

public List<IdentUser> getAllUsers() {
    return this.em.createQuery("from IdentUser u LEFT JOIN FETCH u.employers WHERE u.enabled = 1 AND u.accountNonLocked=0 ").getResultList();
}

JSF UISelectMany компонент, вызывающий исключение при отправке:

<p:selectManyMenu value="#{bean.user.employers}" converter="#{entityConverter}">
    <f:selectItems value="#{bean.companies}" var="company" itemValue="#{company}" itemLabel="#{company.name}"/>
</p:selectManyMenu>

Соответствующая часть стека трассировки:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection, could not initialize proxy - no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:566)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:186)
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:545)
    at org.hibernate.collection.internal.PersistentSet.add(PersistentSet.java:206)
    at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel(MenuRenderer.java:382)
    at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValue(MenuRenderer.java:129)
    at com.sun.faces.renderkit.html_basic.MenuRenderer.getConvertedValue(MenuRenderer.java:315)
    at org.primefaces.component.selectmanymenu.SelectManyMenuRenderer.getConvertedValue(SelectManyMenuRenderer.java:37)
    ...

Как это вызвано и как я могу решить это?

2 ответа

Решение

При отправке JSF UISelectMany Компоненты должны создать новый экземпляр коллекции с предварительно заполненными отправленными и преобразованными значениями. Он не будет очищать и повторно использовать существующую коллекцию в модели, поскольку это может либо отразиться в других ссылках на ту же коллекцию, либо может потерпеть неудачу с UnsupportedOperationException потому что коллекция не поддается изменению, например, полученные Arrays#asList() или же Collections#unmodifiableList(),

MenuRenderer Рендерер позади UISelectMany (а также UISelectOne) компоненты, отвечающие за все это, по умолчанию создадут новый экземпляр коллекции на основе коллекции getClass().newInstance(), Это в свою очередь не с LazyInitializationException если getClass() возвращает реализацию Hibernate's PersistentCollection который используется Hibernate для заполнения свойства коллекции объекта. add() Метод должен инициализировать основной прокси через текущий сеанс, но его нет, потому что задание не выполняется в методе транзакционного сервиса.

Чтобы переопределить это поведение по умолчанию MenuRenderer вам нужно явно указать FQN желаемого типа коллекции через collectionType атрибут UISelectMany составная часть. Для List свойство, вы хотели бы указать java.util.ArrayList и для Set свойство, вы хотели бы указать java.util.LinkedHashSet (или же java.util.HashSet если порядок не важен):

<p:selectManyMenu ... collectionType="java.util.LinkedHashSet">

То же самое относится ко всем остальным UISelectMany компоненты, которые напрямую связаны с управляемым Hibernate объектом JPA. Например:

<p:selectManyCheckbox ... collectionType="java.util.LinkedHashSet">
<h:selectManyCheckbox ... collectionType="java.util.LinkedHashSet">
<h:selectManyListbox ... collectionType="java.util.LinkedHashSet">
<h:selectManyMenu ... collectionType="java.util.LinkedHashSet">

Смотрите также документацию VDL среди других <h:selectManyMenu>, К сожалению, это не указано в документации VDL <p:selectManyMenu>, но так как они используют один и тот же рендерер для конвертации, он должен работать. Если IDE дергается из-за неизвестного collectionType атрибут и раздражающе подчеркивает его, хотя он работает, когда вы игнорируете и запускаете его, а затем используйте <f:attribute> вместо.

<p:selectManyMenu ... >
    <f:attribute name="collectionType" value="java.util.LinkedHashSet" />
    ...
</p:selectManyMenu>

Решение: заменить editUserBehavior.currentUser.employers с коллекцией, которая не управляется Hibernate.

Зачем? Когда сущность становится управляемой, Hibernate заменяет вашу HashSet со своей собственной реализацией Set (будь то PersistentSet). Анализируя реализацию JSF MenuRenderer получается, что в какой-то момент он создает новые Set рефлекторно. Смотрите комментарий в MenuRenderer.convertSelectManyValuesForModel()

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

Во время строительства PersistentSetinitialize() вызывается и - поскольку этот класс предназначен только для вызова из Hibernate - создается исключение LazyInitializationException.

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

Другие вопросы по тегам