3

我使用 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是由 my 提供的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>下面的 Controle Entity 中显示的内容

@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 时controle.xhtml,我收到了 ajax 响应错误。下边是controle.xhtml

<?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>

使用调试器,我能够在第一次抛出异常时确定 Stacktrace

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  

现在我确实意识到关于 SO 的许多帖子都涉及该异常。所有答案通常都围绕正在关闭的实体管理器(或 Hibernate 方言中的会话)。他们提供了两种解决方案:使用 OpenSessionInView 或懒惰地获取属性。我认为我的问题有几个不同的原因。

  1. 我已经使用 OpenSessionInView。此外,日志确实显示,当问题出现时,事务仍然处于打开状态。
  2. 试图懒惰地获取themes属性无济于事

通过代码,在我看来这对所有SelectMultiXXX小部件都是通用的,所以我尝试使用 SelectManyCheckbox。它也没有用。我也尝试过在小部件上使用或不使用特定的转换器,结果是一样的。

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

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

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

_SharedRendererUtils.getConvertedUISelectManyValue(FacesContext, UISelectMany, String[], boolean) (line 143)

如果有人想试一试,这里有一些来自该方法调试的指示

  1. Class<?> modelType = expression.getType(facesContext.getELContext());java.util.List
  2. Collection.class.isAssignableFrom(modelType)是真的
  3. collectionTypeAttr != null是假的
  4. Collection.class.isAssignableFrom(modelType)是真的
  5. Collection<?> componentValue = (Collection<?>) component.getValue();是一个PersistenBag似乎用 data ( storedSnapshotand role) 正确初始化但带有 null 的session
  6. targetForConvertedValues = (componentValue != null ? componentValue.getClass() : modelType).newInstance();最终成为一个PersistentBag一切都为空(包括数据)的人。这可能是问题吗?
  7. boolean isArray = (targetForConvertedValues.getClass().isArray());是假的
  8. ((Collection) targetForConvertedValues).add(value);是一切出错的地方。

有什么想法吗 ?

4

2 回答 2

2

对我来说,这看起来像是我在 Mojarra 中发现的一个非常讨厌的错误,在这个版本的 MyFaces 中可能是相同的。基础是当它进行验证时,它会复制列表,但它使用集合的具体类型,使用无参数构造函数来创建集合。在休眠的情况下,这个新列表没有运行所有的初始化代码,也没有链接回会话。我花了很长时间调试和 Mojarra 源来弄清楚实际发生了什么。

我发现我必须使用 collectionType 属性并将其设置为 java.util 接口类型。在没有明确告诉 JSF 要使用的集合类型的情况下,我不再对集合做任何事情。

于 2012-11-28T14:34:16.677 回答
0

默认情况下,当您从 db 接收包含集合的对象时,Hibernate 中的集合会延迟加载,而不是 List<> 或 Set<>,存在该列表或集合的代理。在调用该集合的 getter 方法时,它将被获取。但是,如果生成对象的会话已关闭或不再可访问(这不应该在您的 OpenSessionInView 过滤器的约束中),则会出现问题。

session.merge如果它们由不同的会话处理,您可以尝试调用您的对象。或者您可以在获取期间手动调用getter您的列表,这将触发代理。或者您可以添加FetchType.Eager您的集合,在这种情况下,即使在获取时对象也不会是代理而是真实对象。

但是我看到您正在使用 Weld 进行 CDI,为什么不使用支持事务管理的 Seam Persistence 模块呢?然后,您将能够非常快速地实施您的开放会话视图方法。

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

于 2012-11-27T14:25:29.763 回答