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

时间:2014-08-11 14:14:59

标签: jsf jpa lazy-loading eager-loading selectmanymenu

尽管有FetchType.EAGERJOIN FETCH,但我通过JSF LazyInitalizationException组件向@ManyToMany集合中添加了一些对象时获得了UISelectMany例如<p:selectManyMenu>

@Entity IdentUserFetchType.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 CompanyFetchType.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)
    ...

这是如何引起的?如何解决?

2 个答案:

答案 0 :(得分:28)

在提交时,JSF UISelectMany组件需要创建集合的全新实例,并提交已提交和转换的值。它不会清除并重用模型中的现有集合,因为它可能会反映在同一集合的其他引用中,或者可能因{8}而失败,因为集合是不可修改的,例如通过UnsupportedOperationExceptionArrays#asList()

MenuRendererUISelectMany(和UISelectOne)组件背后的渲染器,负责这一切,默认情况下会根据集合{{1创建一个全新的集合实例}}。如果Collections#unmodifiableList()返回Hibernate的PersistentCollection实现,而Hibernate内部使用它来填充实体的集合属性,那么这将随getClass().newInstance()失败。 LazyInitializationException方法需要通过当前会话初始化底层代理,但没有,因为作业不是在事务服务方法中执行。

要覆盖getClass()的此默认行为,您需要显式通过add()组件的MenuRenderer属性指定所需集合类型的FQN 。对于collectionType属性,您要指定UISelectMany,对于List属性,您要指定java.util.ArrayList(或Set,如果订购并不重要):

java.util.LinkedHashSet

这同样适用于所有其他java.util.HashSet组件,它们直接绑定到Hibernate管理的JPA实体。 E.g:

<p:selectManyMenu ... collectionType="java.util.LinkedHashSet">

另见VDL documentation of among others <h:selectManyMenu>。遗憾的是,VDL documentation of <p:selectManyMenu>未指定这一点,但由于它们使用相同的渲染器进行转换,因此必须正常工作。如果IDE对某个未知的UISelectMany属性产生了影响并且烦人地强调它,即使它在你忽略它的情况下工作也是如此,那么请改用<p:selectManyCheckbox ... collectionType="java.util.LinkedHashSet"> <h:selectManyCheckbox ... collectionType="java.util.LinkedHashSet"> <h:selectManyListbox ... collectionType="java.util.LinkedHashSet"> <h:selectManyMenu ... collectionType="java.util.LinkedHashSet">

collectionType

答案 1 :(得分:6)

解决方案:将editUserBehavior.currentUser.employers替换为非Hibernate管理的集合。

为什么呢?当实体变得受管理时,Hibernate会将HashSet替换为其Set的实现(无论是PersistentSet)。通过分析JSF MenuRenderer的实现,结果发现它在某一点上反射地创建了新的Set。请参阅MenuRenderer.convertSelectManyValuesForModel()

中的评论
  

//尝试反映无参数构造函数并调用(如果可用)

在构造期间调用PersistentSet initialize()并且 - 因为此类仅用于从Hibernate调用 - 抛出LazyInitializationException。

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