LazyInitializationException при использовании JSF SelectManyMenu.

Описание проблемы

Имеется коллекция сущностей (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>

Теги: