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.
- I already use OpenSessionInView. Furthermore, the logs indeed show that when the problem arises, the transaction is still open.
- 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
Class<?> modelType = expression.getType(facesContext.getELContext());
isjava.util.List
Collection.class.isAssignableFrom(modelType)
is truecollectionTypeAttr != null
is falseCollection.class.isAssignableFrom(modelType)
is trueCollection<?> componentValue = (Collection<?>) component.getValue();
is aPersistenBag
which seems correctly initialized with data (storedSnapshot
androle
) but with a nullsession
.targetForConvertedValues = (componentValue != null ? componentValue.getClass() : modelType).newInstance();
ends up being aPersistentBag
with everything null (including data). Could that be the problem ?boolean isArray = (targetForConvertedValues.getClass().isArray());
is false((Collection) targetForConvertedValues).add(value);
is where everything goes awry.
Any thoughts ?