我使用JSF 2(MyFaces 2.1.7和Primefaces 3.4.2),CDI(Weld-servlet 1.1.10),JPA 2(Hibernate 4.1.7)和Lombok 0.11.2。所有这些都在Tomcat 6和7上运行。
我使用OpenSessionInView模式,通过Filter
实现。
@Advanced
@Data
@Slf4j
public class TransactionalFilter implements Filter, Serializable {
private static final long serialVersionUID = 999173590695648899L;
@Inject
private EntityManager em;
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
boolean newTransaction = false;
EntityTransaction tx = em.getTransaction();
if (!tx.isActive()) {
tx.begin();
newTransaction = true;
}
try {
chain.doFilter(request, response);
if (newTransaction && tx.isActive()) {
tx.commit();
}
} catch (Exception e) {
if (newTransaction && tx.isActive()) {
tx.rollback();
}
throw new ServletException(e);
}
}
(...)
}
注入的RequestScoped
EntityManager
由我的EntityManagerFactory
提供,@ApplicationScoped
@Data
@Slf4j
public class TransactionalEntityManagerFactory implements Serializable {
private static final String PU_NAME = "fr.senat.dosleg";
private static final long serialVersionUID = -3595175390458199193L;
private EntityManagerFactory emf;
/** (...)
* @return un nouvel EntityManager.
*/
@Produces
@RequestScoped
public EntityManager getEntityManager() {
if (emf == null) {
emf = Persistence.createEntityManagerFactory(PU_NAME);
}
return emf.createEntityManager();
}
/**(...)
* @param em le gestionnaire d'entité à libérer.
*/
public void closeEntityManager(@Disposes EntityManager em) {
if (em != null && em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
if (em != null && em.isOpen()) {
em.close();
}
}
}
也为我的所有服务前端提供。
List<Theme>
这一切都运行正常,直到我添加了下面的控制实体中显示的@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = { "organisme", "groupePolitique", "lois", "livrables",
"acteurs", "themes" })
public class Controle implements Serializable {
private static final long serialVersionUID = -6471695606036735891L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@NotNull
@Size(max = 256, message = "trop long.")
private String libelle;
@Pattern(regexp = Constants.URL_PATTERN, message = "pas au bon format")
private String url;
@NotNull
@Type(type = "fr.senat.util.hibernate.usertype.OuiNonSmallType")
private boolean initiativeDesGroupes;
@NotNull
@Type(type = "fr.senat.util.hibernate.usertype.OuiNonSmallType")
private boolean courDesComptes;
@NotNull
private int anneeCreation;
@Embedded
private EcheanceControle echeance = new EcheanceControle();
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "ORGCOD")
private Organisme organisme;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "GRPPOL")
private GroupePolitique groupePolitique;
@ManyToMany
@JoinTable(name = "CONTROLE_LOI", joinColumns = @JoinColumn(name = "CON_ID"), inverseJoinColumns = @JoinColumn(name = "LOICOD"))
private List<Loi> lois;
@OneToMany(orphanRemoval = true, cascade = CascadeType.ALL)
@JoinColumn(name = "CON_ID", nullable = false)
private List<LivrableControle> livrables;
@OneToMany(orphanRemoval = true, cascade = CascadeType.ALL)
@JoinColumn(name = "CON_ID", nullable = false)
private List<ActeurControle> acteurs;
@ManyToMany
@JoinTable(name = "THEME_CONTROLE", joinColumns = @JoinColumn(name = "CON_ID"), inverseJoinColumns = @JoinColumn(name = "THECLE"))
private List<Theme> themes;
(...)
}
controle.xhtml
现在,当我尝试保存现有控件时,通过controle.xhtml
页面,我收到ajax响应错误。以下是<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:s="http://www.senat.fr/taglib/util"
xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition template="template.xhtml">
<ui:define name="title">
<h:outputText value="#{controle.controle.libelle}"
rendered="#{not empty controle.controle.id}" />
<h:outputText value="Création d'un contrôle"
rendered="#{empty controle.controle.id}" />
</ui:define>
<ui:define name="specific_header">
<h:outputScript library="fr.senat.util.primefaces"
name="calendar-locale.js" />
</ui:define>
<ui:define name="content">
<h:form id="controle">
<p:messages />
<p:panel>
<h:panelGrid columns="2" columnClasses="label,">
<p:outputLabel for="libelle" value="Libellé" />
<p:inputText id="libelle" value="#{controle.controle.libelle}"
size="60" />
<p:outputLabel for="url" value="URL" />
<p:inputText id="url" value="#{controle.controle.url}" size="60" />
<h:outputText value="Acteurs" />
<h:panelGroup>
<h:panelGroup id="acteurs">
<p:dataTable var="a" value="#{controle.controle.acteurs}"
rendered="#{not empty controle.controle.acteurs}">
<p:column headerText="Rôle">
<h:outputText value="#{a.role}" />
</p:column>
<p:column headerText="Nom">
<h:outputText
value="#{a.senateur.nomUsuel} #{a.senateur.prenomUsuel}" />
</p:column>
<p:column headerText="Enlever">
<p:commandButton
title="Enlever #{a.senateur.nomUsuel} #{a.senateur.prenomUsuel}"
icon="ui-icon-trash"
actionListener="#{controle.removeElement}"
update=":controle:acteurs" immediate="true">
<f:attribute name="ancien" value="#{a}" />
</p:commandButton>
</p:column>
</p:dataTable>
</h:panelGroup>
<p:commandButton id="addActeur" value="Ajouter un acteur"
icon="ui-icon-plus" onclick="addActeurDialog.show()"
immediate="true" />
</h:panelGroup>
<p:outputLabel for="themes" value="Thèmes" />
<p:selectManyButton id="themes" value="#{controle.controle.themes}">
<f:selectItems value="#{controle.themes}" var="t"
itemLabel="#{t.libelle}" itemValue="#{t}" />
</p:selectManyButton>
</h:panelGrid>
<f:facet name="footer">
<p:commandButton onclick="deleteControleDialog.show()"
value="Supprimer" icon="ui-icon-trash"
styleClass="ui-priority-secondary" type="button" />
<p:button outcome="pretty:start" value="Annuler"
icon="ui-icon-cancel" />
<p:commandButton id="saveSubmit" value="Sauvegarder"
actionListener="#{controle.save}" icon="ui-icon-disk"
styleClass="ui-priority-primary" />
</f:facet>
</p:panel>
<p:defaultCommand target="saveSubmit" />
</h:form>
<p:dialog header="Ajoute un nouvel acteur" id="addActeurDialog"
widgetVar="addActeurDialog" modal="true" width="650">
<h:form id="addActeurForm">
<p:messages />
<h:panelGrid columns="2">
<p:outputLabel for="newRole" value="Rôle" />
<p:autoComplete id="newRole" dropdown="true"
value="#{controle.newRole}"
completeMethod="#{controle.completeRole}" />
<p:outputLabel for="senateurSearch" value="Sénateur" />
<h:panelGroup>
<p:inputText id="senateurSearch"
value="#{controle.senateurSearch}" size="30" />
<p:commandButton id="senateurSearchSubmit" value="Chercher"
actionListener="#{controle.searchSenateurs}"
update="senateursFound" icon="ui-icon-search"
oncomplete="addActeurDialog.initPosition()" />
<p:selectBooleanButton value="#{controle.senateurSearchActif}"
onLabel="en activité seulement" offLabel="en activité ou non" />
</h:panelGroup>
</h:panelGrid>
<h:panelGroup id="senateursFound">
<p:dataTable value="#{controle.senateurs}" var="s"
rendered="#{not empty controle.senateurs}" rows="10">
<p:column headerText="Nom">
<h:outputText value="#{s.nomCompletUsuel}" />
</p:column>
<p:column headerText="Ajout">
<p:commandButton title="Ajouter le sénateur #{s.nomCompletUsuel}"
icon="ui-icon-plus" actionListener="#{controle.addElement}"
oncomplete="addActeurDialog.hide()" update=":controle:acteurs">
<f:attribute name="nouveau" value="#{s}" />
</p:commandButton>
</p:column>
</p:dataTable>
</h:panelGroup>
</h:form>
</p:dialog>
<p:confirmDialog id="deleteControleDialog"
message="Etes-vous sûr de vouloir supprimer ce contrôle ?"
header="Suppression du contrôle" severity="alert"
widgetVar="deleteControleDialog">
<p:commandButton value="Annuler"
onclick="deleteControleDialog.hide()" />
<p:commandButton value="Confirmer la suppression"
action="#{controle.deleteControle}" styleClass="ui-priority-primary" />
</p:confirmDialog>
</ui:define>
</ui:composition>
</html>
<?xml version="1.0" encoding="utf-8"?>
<partial-response>
<error>
<error-name>org.hibernate.LazyInitializationException</error-name>
<error-message><![CDATA[could not initialize proxy - no Session]]></error-message>
</error>
</partial-response>
这是错误
PersistentBag(AbstractPersistentCollection).withTemporarySessionIfNeeded(LazyInitializationWork<T>) line: 180
PersistentBag(AbstractPersistentCollection).initialize(boolean) line: 520
PersistentBag(AbstractPersistentCollection).write() line: 345
PersistentBag.add(Object) line: 291
_SharedRendererUtils.getConvertedUISelectManyValue(FacesContext, UISelectMany, String[], boolean) line: 339
RendererUtils.getConvertedUISelectManyValue(FacesContext, UISelectMany, Object, boolean) line: 1088
RendererUtils.getConvertedUISelectManyValue(FacesContext, UISelectMany, Object) line: 1056
HtmlCheckboxRenderer(HtmlCheckboxRendererBase).getConvertedValue(FacesContext, UIComponent, Object) line: 525
SelectManyButtonRenderer.getConvertedValue(FacesContext, UIComponent, Object) line: 36
SelectManyButton(UISelectMany).getConvertedValue(FacesContext, Object) line: 402
SelectManyButton(UIInput).validate(FacesContext) line: 584
SelectManyButton(UISelectMany).validate(FacesContext) line: 393
SelectManyButton(UIInput).processValidators(FacesContext) line: 274
HtmlPanelGrid(UIComponentBase).processValidators(FacesContext) line: 1421
Panel(UIComponentBase).processValidators(FacesContext) line: 1421
Panel.processValidators(FacesContext) line: 297
HtmlForm(UIForm).processValidators(FacesContext) line: 209
HtmlBody(UIComponentBase).processValidators(FacesContext) line: 1421
UIViewRoot(UIComponentBase).processValidators(FacesContext) line: 1421
UIViewRoot._processValidatorsDefault(FacesContext) line: 1401
UIViewRoot.access$500(UIViewRoot, FacesContext) line: 74
UIViewRoot$ProcessValidatorPhaseProcessor.process(FacesContext, UIViewRoot) line: 1508
UIViewRoot._process(FacesContext, PhaseId, UIViewRoot$PhaseProcessor) line: 1357
UIViewRoot.processValidators(FacesContext) line: 799
ProcessValidationsExecutor.execute(FacesContext) line: 38
LifecycleImpl.executePhase(FacesContext, PhaseExecutor, PhaseListenerManager) line: 170
LifecycleImpl.execute(FacesContext) line: 117
CodiLifecycleWrapper.execute(FacesContext) line: 95
FacesServlet.service(ServletRequest, ServletResponse) line: 197
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 290
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
PrettyFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 145
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 235
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
ApplicationDispatcher.invoke(ServletRequest, ServletResponse, ApplicationDispatcher$State) line: 646
ApplicationDispatcher.processRequest(ServletRequest, ServletResponse, ApplicationDispatcher$State) line: 436
ApplicationDispatcher.doForward(ServletRequest, ServletResponse) line: 374
ApplicationDispatcher.forward(ServletRequest, ServletResponse) line: 302
PrettyFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 137
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 235
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
TransactionalFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 60
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 235
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
SetUtf8CharacterEncodingFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 44
(...)
Thread.run() line: 662
使用调试器,我可以在首次抛出异常时确定Stacktrace
themes
现在我确实认识到SO上的很多帖子都关注Exception。所有答案通常围绕实体管理器(或Hibernate方言中的会话)被关闭。它们提供了两种解决方案:懒惰地使用OpenSessionInView或获取属性。我认为我的问题因为几个原因而有所不同。
_SharedRendererUtils.getConvertedUISelectManyValue(FacesContext, UISelectMany, String[], boolean) (line 143)
属性无效通过代码,在我看来,这对所有SelectMultiXXX小部件都是通用的,所以我尝试使用SelectManyCheckbox。它也没用。我还试图在小部件上使用特定的转换器,结果是一样的。
最后一点,我想指出所有其他List&lt;&gt;属性在同一个controle.xhtml上工作。然而,差异可能是通过对话框修改这些(因此不同的请求)。
我希望你能看到我没有的东西,或者你可以确认它是一个bug,或者你可以为我提供一个解决方法。如果你不这样做,我仍然感谢你花时间阅读这个长期的问题。
更新29/11/2012 :我现在确定实体经理在异常时确实是开放的。此外,我确信问题来自以下方法
Class<?> modelType = expression.getType(facesContext.getELContext());
如果有人想要试一试
,这里有一些关于该方法调试的指示java.util.List
是Collection.class.isAssignableFrom(modelType)
collectionTypeAttr != null
是真的Collection.class.isAssignableFrom(modelType)
是假的Collection<?> componentValue = (Collection<?>) component.getValue();
是真的PersistenBag
是一个storedSnapshot
,似乎已正确初始化了数据(role
和session
)但空格为targetForConvertedValues = (componentValue != null ? componentValue.getClass() : modelType).newInstance();
。PersistentBag
最终成为boolean isArray = (targetForConvertedValues.getClass().isArray());
,其中包含所有内容(包括数据)。 可能是问题吗? ((Collection) targetForConvertedValues).add(value);
是假的有什么想法吗?
答案 0 :(得分:2)
这对我来说看起来像是我在Mojarra中发现的一个非常讨厌的错误,在这个版本的MyFaces中可能是相同的。基础是当它进行验证时它会执行列表的副本,但它使用集合的具体类型,使用no-arg构造函数来创建集合。在休眠的情况下,这个新列表并没有在其上运行所有init代码,并且它没有链接回会话。这对我来说是很长时间的调试和Mojarra来源,以弄清楚实际发生了什么。
我发现我必须使用collectionType属性并将其设置为java.util接口类型。我不再对集合做任何事情,而没有明确地告诉JSF要使用的集合类型。
答案 1 :(得分:0)
当您从包含集合的db接收对象时,默认情况下会延迟加载Hibernate中的集合,而不是List&lt;&gt;或设置&lt;&gt;该列表或集合有代理。在调用该集合的getter方法时,将获取它。但是,如果对象源自的会话已关闭或不再可访问(不应该在OpenSessionInView过滤器的约束中),则会出现问题。
如果对象由不同的会话处理,您可以尝试在对象上调用session.merge
。或者您可以在提取期间手动调用列表的getter
,这将触发代理。或者您可以在集合中添加FetchType.Eager
,在这种情况下,即使在提取时,对象也不是代理,而是真实对象。
但我发现你使用Weld进行CDI,为什么不使用支持事务管理的Seam Persistence模块?然后,您将能够非常快速地在视图方法中实现您的开放会话。