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)
...
Как это вызвано и как я могу его решить?
Ответы
Ответ 1
При отправке компонентам JSF UISelectMany
необходимо создать новый экземпляр коллекции с предварительно заполненными и преобразованными значениями. Он не будет очищать и повторно использовать существующую коллекцию в модели, поскольку она может быть отражена в других ссылках на один и тот же сборник или может потерпеть неудачу с помощью UnsupportedOperationException
, потому что коллекция не поддается изменению, например, полученная с помощью Arrays#asList()
или Collections#unmodifiableList()
.
MenuRenderer
, средство рендеринга UISelectMany
(и UISelectOne
), компоненты, ответственные за все это, по умолчанию создадут новый экземпляр коллекции на основе коллекция getClass().newInstance()
. Это, в свою очередь, потерпит неудачу с LazyInitializationException
, если getClass()
возвращает реализацию Hibernate 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
, которые напрямую привязаны к сущности JPA, управляемой Hibernate. Например:
<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>
Ответ 2
Решение: замените editUserBehavior.currentUser.employers
на коллекцию, которая не управляется Hibernate.
Почему? Когда Entity становится управляемым, Hibernate заменяет ваш HashSet
своей собственной реализацией Set
(be it PersistentSet
). Анализируя реализацию JSF MenuRenderer
, оказывается, что в какой-то момент он создает новый Set
рефлексивно. См. Комментарий в MenuRenderer.convertSelectManyValuesForModel()
//пытаемся отразить конструктор без аргументов и вызывать, если доступно
При построении PersistentSet
initialize()
вызывается и - поскольку этот класс предназначен только для вызова из Hibernate - LazyInitializationException, вызывается.
Примечание. Это только мое подозрение. Я не знаю ваших версий JSF и Hibernate, но это более вероятно.