com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel上的org.hibernate.LazyInitializationException

cod*_*pet 14 jsf jpa lazy-loading eager-loading selectmanymenu

尽管FetchType.EAGERJOIN 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>();
Run Code Online (Sandbox Code Playgroud)

@Entity Company,有FetchType.EAGER:

@ManyToMany(mappedBy="employers", fetch=FetchType.EAGER)
private List<IdentUser> employee;
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

UISelectMany在提交时导致异常的JSF 组件:

<p:selectManyMenu value="#{bean.user.employers}" converter="#{entityConverter}">
    <f:selectItems value="#{bean.companies}" var="company" itemValue="#{company}" itemLabel="#{company.name}"/>
</p:selectManyMenu>
Run Code Online (Sandbox Code Playgroud)

堆栈跟踪的相关部分:

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)
    ...
Run Code Online (Sandbox Code Playgroud)

这是怎么造成的,我该如何解决?

Bal*_*usC 29

提交时,JSF UISelectMany组件需要创建一个全新的集合实例,并提交已提交和转换的值.它不会清除和重用现有的集合中的模型,可能要么得到体现在其他引用同一个集合,或可能会失败,UnsupportedOperationException因为集合是不可修改的,如通过获得的那些Arrays#asList()Collections#unmodifiableList().

MenuRenderer,后面的渲染器UISelectMany(和UISelectOne)组件谁负责这一切,将默认创建基于集合的集合的一个全新的实例getClass().newInstance().LazyInitializationException如果getClass()返回Hibernate的实现,而Hibernate PersistentCollection内部使用它来填充实体的集合属性,那么这将失败.该add()方法即需要通过当前会话初始化底层代理,但没有,因为作业不是在事务服务方法中执行的.

要覆盖此默认行为MenuRenderer,您需要通过组件的属性显式指定所需集合类型的FQN .对于一个属性,你想指定并为财产,你想指定(或如果顺序并不重要):collectionTypeUISelectManyListjava.util.ArrayListSetjava.util.LinkedHashSetjava.util.HashSet

<p:selectManyMenu ... collectionType="java.util.LinkedHashSet">
Run Code Online (Sandbox Code Playgroud)

这同样适用于所有其他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">
Run Code Online (Sandbox Code Playgroud)

另请参阅VDL文档等<h:selectManyMenu>.遗憾的是,这并未在VDL文档中<p:selectManyMenu>指定,但由于它们使用相同的渲染器进行转换,因此必须正常工作.如果IDE正在对一个未知collectionType属性进行抽搐并且恼人地强调它,即使它在你忽略它运行它时也能正常工作,那么请改用它<f:attribute>.

<p:selectManyMenu ... >
    <f:attribute name="collectionType" value="java.util.LinkedHashSet" />
    ...
</p:selectManyMenu>
Run Code Online (Sandbox Code Playgroud)


zbi*_*big 6

解决方案:替换editUserBehavior.currentUser.employers不受Hibernate管理的with集合.

为什么?当实体变得受管理时,Hibernate 会将HashSet其替换为自己的实现Set(是它PersistentSet).通过分析JSF的实现MenuRenderer,事实证明,它一度创造了新的Set反思性.请参阅评论MenuRenderer.convertSelectManyValuesForModel()

//尝试反映无参数构造函数并在可用时调用

PersistentSet initialize()调用构造期间- 由于此类仅用于从Hibernate调用 - 抛出了LazyInitializationException.

注意:这只是我的怀疑.我不知道你的JSF和Hibernate的版本,但更有可能的是这种情况.