“无法使用开放会话初始化代理 - 无会话”

时间:2012-11-27 14:06:57

标签: hibernate jsf jpa cdi myfaces

我使用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或获取属性。我认为我的问题因为几个原因而有所不同。

  1. 我已经使用了OpenSessionInView。此外,日志确实表明,当问题出现时,交易仍然是开放的。
  2. 尝试懒惰地获取_SharedRendererUtils.getConvertedUISelectManyValue(FacesContext, UISelectMany, String[], boolean) (line 143) 属性无效
  3. 通过代码,在我看来,这对所有SelectMultiXXX小部件都是通用的,所以我尝试使用SelectManyCheckbox。它也没用。我还试图在小部件上使用特定的转换器,结果是一样的。

    最后一点,我想指出所有其他List&lt;&gt;属性在同一个controle.xhtml上工作。然而,差异可能是通过对话框修改这些(因此不同的请求)。

    我希望你能看到我没有的东西,或者你可以确认它是一个bug,或者你可以为我提供一个解决方法。如果你不这样做,我仍然感谢你花时间阅读这个长期的问题。

    更新29/11/2012 :我现在确定实体经理在异常时确实是开放的。此外,我确信问题来自以下方法

    Class<?> modelType = expression.getType(facesContext.getELContext());

    如果有人想要试一试

    ,这里有一些关于该方法调试的指示
    1. java.util.ListCollection.class.isAssignableFrom(modelType)
    2. collectionTypeAttr != null是真的
    3. Collection.class.isAssignableFrom(modelType)是假的
    4. Collection<?> componentValue = (Collection<?>) component.getValue();是真的
    5. PersistenBag是一个storedSnapshot,似乎已正确初始化了数据(rolesession)但空格为targetForConvertedValues = (componentValue != null ? componentValue.getClass() : modelType).newInstance();
    6. PersistentBag最终成为boolean isArray = (targetForConvertedValues.getClass().isArray());,其中包含所有内容(包括数据)。 可能是问题吗?
    7. ((Collection) targetForConvertedValues).add(value);是假的
    8. {{1}}是一切都出错的地方。
    9. 有什么想法吗?

2 个答案:

答案 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模块?然后,您将能够非常快速地在视图方法中实现您的开放会话。

http://www.seamframework.org/Seam3/PersistenceModule