“could not initialize proxy - no session” with an open session available

孤人 提交于 2020-01-13 18:23:37

问题


I work with JSF 2 (MyFaces 2.1.7 and Primefaces 3.4.2), CDI (Weld-servlet 1.1.10), JPA 2 (Hibernate 4.1.7) and Lombok 0.11.2. All this runs on Tomcat 6 and 7.

I use the OpenSessionInView pattern, implemented through a 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);
        }

    }
    (...)
}

The injected RequestScoped EntityManager is provided by my EntityManagerFactory, who also provides it for all my services fronts.

@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();
        }
    }
}

This all worked fine, until I added the List<Theme> shown in the Controle Entity below

@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;

    (...)

}

Now when I try to save an existing Controle, through the controle.xhtml page, I get an ajax response error. Below is 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>

and this is the error

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

With a debugger I was able to determine the Stacktrace when the Exception is first thrown

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  

Now I do realise that many posts on SO concern that Exception. All answers generally revolve around the entitymanager (or session in Hibernate dialect) being closed. They provide two solutions : use OpenSessionInView or fetch properties lazyly. I think my problem is different for several reasons.

  1. I already use OpenSessionInView. Furthermore, the logs indeed show that when the problem arises, the transaction is still open.
  2. Trying to lazyly fetch the themes properties does not help

Going through the code, it seemed to me that this was generic to all SelectMultiXXX widgets, so I tried with SelectManyCheckbox. It didn't work either. I have also tried to use a specific converter or not on the widget and the result is the same.

On a final note, I wanted to point out that all the other List<> properties work on the same controle.xhtml. The difference however might be that these are modified through dialog boxes (hence different requests).

I hope that you can see something that I don't, or that you can confirm it is a bug, or that you can provide me with a workaround. If you don't, I still thank you for the time you have taken to read that long question.

UPDATE 29/11/2012 : I am now certain the entitymanager is indeed open at the time of the exception. Furthermore, I am convinced that the problem comes from the following method

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

Here are a few pointers from the debug of that method if somebody wants to give it a go

  1. Class<?> modelType = expression.getType(facesContext.getELContext()); is java.util.List
  2. Collection.class.isAssignableFrom(modelType) is true
  3. collectionTypeAttr != null is false
  4. Collection.class.isAssignableFrom(modelType) is true
  5. Collection<?> componentValue = (Collection<?>) component.getValue(); is a PersistenBag which seems correctly initialized with data (storedSnapshot and role) but with a null session.
  6. targetForConvertedValues = (componentValue != null ? componentValue.getClass() : modelType).newInstance(); ends up being a PersistentBag with everything null (including data). Could that be the problem ?
  7. boolean isArray = (targetForConvertedValues.getClass().isArray()); is false
  8. ((Collection) targetForConvertedValues).add(value); is where everything goes awry.

Any thoughts ?


回答1:


This to me looks like a really nasty bug I found in Mojarra, could be the same in this version of MyFaces. The basics are when it does validation it does a copy of the list, but it uses the concrete type of the collection, using the no-arg constructor to create the collection. In hibernate's case, this new list doesn't have all the init code run on it and it doesn't link back up to the session. It to me a long time debugging and the Mojarra source to figure out what was actually happening.

I found I had to use the collectionType attribute and set it to the java.util interface type. I don't do anything with collections anymore without explicitly telling JSF the collection type to use.




回答2:


Collections in Hibernate are by default lazily loaded when you receive an object from db that contains a collection, in place of the List<> or Set<> there is a proxy for that list or set. Upon calling the getter method for that collection, it will be fetched. However, a problem would be if the session from which the object originated, is closed or no longer accessible (which should not be in the constraints of your OpenSessionInView filter).

You can try calling session.merge on your objects if they are handled by different sessions. Or you can manually call the getter of your List during fetching which will trigger the proxy. Or you can add FetchType.Eager on your collection, in which case the object will not be a proxy but a real object even at the time of fetching.

But I see you are using Weld for CDI, why not use Seam Persistence module which has support for transaction management? Then you will be able to implement your open session in view approach very quickly.

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



来源:https://stackoverflow.com/questions/13585998/could-not-initialize-proxy-no-session-with-an-open-session-available

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!