Имеется коллекция сущностей (entity) JPA, которую необходимо использовать в качестве источника данных для компонента SelectManyMenu
. В качестве JPA-провайдера используется Hibernate.
Часть кода JSF (PrimeFaces):
<p:selectManyMenu value="#{sourceBean.entitiesList}" converter="entityConverter">
<f:selectItems value="#{sourceBean.selectedEntities}" var="entity" itemLabel="#{entity.name}" itemValue="#{entity}" />
</p:selectManyMenu>
Компонент SelectManyMenu
позволяет выбирать нужные элементы из списка.
После отправки формы на этапе валидации возникает исключение hibernate.LazyInitializationException: failed to lazily initialize a collection, could not initialize proxy - no Session
.
Загвоздка заключается в том, что сущность содержит только простые типы данных, инициализирующиеся в момент её загрузки. А как известно, данное исключение возникает при попытке доступа к данным, которые не были проинициализированы при доступном persistence context/session.
Для этого проверяем стек методов, который был выдан вместе с исключением:
at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel(MenuRenderer.java:381)
at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValue(MenuRenderer.java:128)
at com.sun.faces.renderkit.html_basic.MenuRenderer.getConvertedValue(MenuRenderer.java:314)
Внимательно посмотрим метод convertSelectManyValuesForModel
, строка 381:
...
Collection targetCollection = null;
// see if the collectionType hint is available, if so, use that
Object collectionTypeHint = uiSelectMany.getAttributes().get("collectionType");
if (collectionTypeHint != null) {
targetCollection = createCollectionFromHint(collectionTypeHint);
} else {
// try to get a new Collection to store the values based
// by trying to create a clone
Collection currentValue = (Collection) uiSelectMany.getValue();
if (currentValue != null) {
targetCollection = cloneValue(currentValue);
}
// No cloned instance so if the modelType happens to represent a
// concrete type (probably not the norm) try to reflect a
// no-argument constructor and invoke if available.
if (targetCollection == null) {
//noinspection unchecked
targetCollection = createCollection(currentValue, modelType);
}
// No suitable instance to work with, make our best guess
// based on the type.
if (targetCollection == null) {
//noinspection unchecked
targetCollection = bestGuess(modelType, values.length);
}
}
//noinspection ManualArrayToCollectionCopy
for (Object v : values) {
//noinspection unchecked
targetCollection.add(v);
}
return targetCollection;
...
Проанализировав этот метод, можно заметить, что создается новая коллекция targetCollection
, в которую и добавляются конвертированные объекты. Тип этой коллекции основан на типе, который передается в качестве значения в наш компонент SelectManyMenu
. В нашем случае этим типом данных является PersistentBag
- собственная реализация списка в Hibernate. Таким образом, получается, что создается экземпляр класса PersistentBag
вне открытого persistence context или сессии, а это приводит к возникновению исключения в момент добавления объекта в коллекцию.
Для этого необходимо явно указать класс объекта targetCollection,
что предусмотрено в методе convertSelectManyValuesForModel:
// see if the collectionType hint is available, if so, use that
Object collectionTypeHint = uiSelectMany.getAttributes().get("collectionType");
if (collectionTypeHint != null) {
targetCollection = createCollectionFromHint(collectionTypeHint);
} else {
...
}
Таким образом, необходимо использовать аттрибут collectionType
для компонента SelectManyMenu
с указанием нужного типа данных:
<p:selectManyMenu value="#{sourceBean.entitiesList}" converter="entityConverter">
<f:attribute name="collectionType" value="java.util.ArrayList" />
<f:selectItems value="#{sourceBean.selectedEntities}"
var="entity" itemLabel="#{entity.name}" itemValue="#{entity}" />
</p:selectManyMenu>