Despite of FetchType.EAGER
and JOIN FETCH
, I get a LazyInitalizationException
while adding some objects to a @ManyToMany
collection via a JSF UISelectMany
component such as in my case the <p:selectManyMenu>
.
The @Entity IdentUser
, with 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>();
The @Entity Company
, with FetchType.EAGER
:
@ManyToMany(mappedBy="employers", fetch=FetchType.EAGER)
private List<IdentUser> employee;
The JPQL, with 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();
}
The JSF UISelectMany
component causing the exception while submitting:
<p:selectManyMenu value="#{bean.user.employers}" converter="#{entityConverter}">
<f:selectItems value="#{bean.companies}" var="company" itemValue="#{company}" itemLabel="#{company.name}"/>
</p:selectManyMenu>
The relevant part of the stack trace:
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)
...
How is this caused and how can I solve it?
While submitting, the JSF
UISelectMany
components need to create a brand new instance of the collection with the submitted and converted values prefilled. It won't clear out and reuse the existing collection in the model as that may either get reflected in other references to the same collection, or may fail with anUnsupportedOperationException
because the collection is unmodifiable, such as the ones obtained byArrays#asList()
orCollections#unmodifiableList()
.The
MenuRenderer
, the renderer behindUISelectMany
(andUISelectOne
) components who's responsible for this all, will by default create a brand new instance of the collection based on collection'sgetClass().newInstance()
. This would in turn fail withLazyInitializationException
if thegetClass()
returns an implementation of Hibernate'sPersistentCollection
which is internally used by Hibernate to fill the collection property of an entity. Theadd()
method namely needs to initialize the underlying proxy via the current session, but there's none because the job isn't performed within a transactional service method.To override this default behavior of
MenuRenderer
, you need to explicitly specify the FQN of the desired collection type via thecollectionType
attribute of theUISelectMany
component. For aList
property, you'd like to specifyjava.util.ArrayList
and for aSet
property, you'd like to specifyjava.util.LinkedHashSet
(orjava.util.HashSet
if ordering isn't important):The same applies to all other
UISelectMany
components as well which are directly tied to a Hibernate-managed JPA entity. E.g:See also the VDL documentation of among others
<h:selectManyMenu>
. This is unfortunately not specified in VDL documentation of<p:selectManyMenu>
, but as they use the same renderer for converting, it must work. If the IDE is jerking about an unknowncollectionType
attribute and annoyingly underlines it even though it works when you ignore'n'run it, then use<f:attribute>
instead.Solution: Replace the
editUserBehavior.currentUser.employers
with collection that is not managed by Hibernate.Why? When the Entity becomes managed, the Hibernate replaces your
HashSet
with its own implementation ofSet
(be itPersistentSet
). By analysing the implementation of JSFMenuRenderer
, it turns out that at one point it creates newSet
reflectively. See the comment inMenuRenderer.convertSelectManyValuesForModel()
During construction of
PersistentSet
initialize()
is invoked and - as this class is only meant to be invoked from Hibernate - LazyInitializationException is thrown.Note: This is my suspicion only. I don't know your versions of JSF and Hibernate but this is more likely the case.