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()
// пытаемся отразить конструктор без аргументов и вызвать, если доступно
Во время строительства PersistentSet
initialize()
вызывается и - поскольку этот класс предназначен только для вызова из Hibernate - создается исключение LazyInitializationException.
Примечание: это только мое подозрение. Я не знаю ваших версий JSF и Hibernate, но это более вероятно.