尽管有FetchType.EAGER
和JOIN FETCH
,但我通过JSF LazyInitalizationException
组件向@ManyToMany
集合中添加了一些对象时获得了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>();
@Entity Company
,FetchType.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)
...
这是如何引起的?如何解决?
答案 0 :(得分:28)
在提交时,JSF UISelectMany
组件需要创建集合的全新实例,并提交已提交和转换的值。它不会清除并重用模型中的现有集合,因为它可能会反映在同一集合的其他引用中,或者可能因{8}而失败,因为集合是不可修改的,例如通过UnsupportedOperationException
或Arrays#asList()
。
MenuRenderer
,UISelectMany
(和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版本,但更有可能是这种情况。