Why is my conversationId incremented or understanding conversations
bolke May 19, 2008 9:49 AMHi,
I am having trouble understanding why a conversationid is incremented sometimes and (?) resulting in the dreaded LazyInitalization. Or in other words why do I end up with a LazyInitalizationException when I do not expect to have left the conversation? Please excuse me if this a question asked before: I did search but I am having trouble applying possible solutions to my situation. So thanks for looking at it.
The setup is quite simple. A logged in user is able to select an Item from a List which is then displayed. This all happens with Ajax calls so the page itself is never fully updated. I have structured the components as follows. The list is contained in a SessionBean, the selected item is scoped Conversation. If I select an Item from the list I often (not always!) receive a LazyInitalizationException on one of the properties of the Item. I don't understand why. The documentation says, afaik, the LazyInitalizationExceptions occur when accessing properties from a different conversation than the current one. For me the question is when did I leave the converstation and why?
. Furthermore, would an EntityHome pattern solve this?
Below is the 'holderpage':
<ui:define name="right"> <a:form id="chemicallist"> <div id="itemList"> <ui:repeat value="#{chemicals}" var="c"> <div class="item"> <div class="un">#{c.un} - </div> <div class="chemicalName"> <a:commandLink reRender="chemicaldetail" action="#{chemicalManager.select(c)}" onclick="highlightItem(this);" eventsQueue="chemicalsProcessor" ajaxSingle="true"> <chemix:translation language="#{localeSelector.language}" value="#{c.translations}" /> </a:commandLink> </div> </div> </ui:repeat> </div> </a:form> </ui:define> <ui:define name="body"> <rich:effect name="showTrans" for="translationsHolder" type="Appear" /> <rich:effect name="hideTrans" for="translationsHolder" type="Fade" /> <a:outputPanel id="chemicaldetail"> <ui:include src="detail/chemicalDetail.xhtml" /> </a:outputPanel> </ui:define>
This is the detail page:
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:s="http://jboss.com/products/seam/taglib" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:a="http://richfaces.org/a4j" xmlns:c="http://java.sun.com/jstl/core" xmlns:rich="http://richfaces.org/rich" xmlns:chemix="http://www.chainsoftware.nl/chemix"> <script language="javascript"> var translations_show = false; function translations() { if (!translations_show) { showTrans({duration:0.7}); translations_show = true; } else { hideTrans(); translations_show = false; } return false; } </script> <h:form> <div class="messages"> <rich:messages errorClass="errorMessage" infoClass="infoMessage"/> </div> <chemix:actionButtons backingBean="#{chemicalManager}" searchPage="/chemicalManager.xhtml" reRender="chemicalDetail" /> <rich:tabPanel switchType="client"> <rich:tab label="#{messages['chemical.details']}"> <table cellpadding="0" cellspacing="1" border="0" class="properties" styleClass="chemicalProperties" width="100%"> <col width="250px" /> <col /> <tbody> <tr> <td>#{messages['chemical.un']}</td> <td> <s:decorate id="chemicalUn" template="/layout/edit.xhtml"> <h:inputText value="#{chemical.un}"> <a:support event="onblur" reRender="chemicalUn" bypassUpdates="true"/> </h:inputText> </s:decorate> </td> </tr> <tr> <td>#{messages['chemical.name']}</td> <td> <a href="#" onclick="translations();"> <h:graphicImage value="img/arrow_forward_details.gif" style="border: none"/> </a> <chemix:translation language="#{localeSelector.language}" value="#{chemical.translations}" /> <div id="translationsHolder" style="display:none"> <a:outputPanel id="translations" styleClass="translations" layout="block"> <c:if test="${!empty chemical}"> <div id="translationsHeader"> <a:commandButton value="#{messages['chemical.addMissingLanguage']}" reRender="translations" /> <h:selectOneMenu> <s:selectItems value="#{chemicalManager.missingLanguages}" var="lang" label="#{lang}" noSelectionLabel="#{messages['chemical.noMissingLanguage']}" /> </h:selectOneMenu> </div> </c:if> <table width="100%" border="0"> <ui:repeat value="#{chemical.translations}" var="trans"> <tr> <td>#{trans.language}:</td> <td><h:inputTextarea value="#{trans.name}" cols="60" rows="5"/></td> </tr> </ui:repeat> </table> </a:outputPanel> </div> </td> </tr> <tr> <td>#{messages['chemical.classificationCode']}</td> <td> <s:decorate id="chemicalClassificationCode" template="/layout/edit.xhtml"> <h:inputText value="#{chemical.classificationCode}"> <a:support event="onblur" reRender="chemicalClassificationCode" bypassUpdates="true"/> </h:inputText> </s:decorate> </td> </tr> <tr> <td>#{messages['chemical.class']}</td> <td> <s:decorate id="chemicalClassification" template="/layout/edit.xhtml"> <h:inputText value="#{chemical.classification}"> <a:support event="onblur" reRender="chemicalClassification" bypassUpdates="true"/> </h:inputText> </s:decorate> </td> </tr> <tr> <td>#{messages['chemical.packingGroup']}</td> <td> <s:decorate id="chemicalPackageGroup" template="/layout/edit.xhtml"> <h:inputText value="#{chemical.packageGroup}"> <a:support event="onblur" reRender="chemicalPackageGroup" bypassUpdates="true"/> </h:inputText> </s:decorate> </td> </tr> <tr> <td>#{messages['chemical.explosionProtectionRequired']}</td> <td><h:selectBooleanCheckbox value="#{chemical.explosionProtection}" /></td> </tr> <tr> <td>#{messages['chemical.explosionGroups']}</td> <td> <h:selectOneMenu value="#{chemical.explosionGroup}"> <s:selectItems value="#{enumLists.explosionGroups}" var="group" label="#{group}" noSelectionLabel="No explosion group"/> <s:convertEnum /> </h:selectOneMenu> </td> </tr> <tr> <td>#{messages['chemical.temperatureClass']}</td> <td> <h:selectOneMenu value="#{chemical.temperatureClass}"> <s:selectItems value="#{enumLists.temperatureClasses}" var="class" label="#{class}" noSelectionLabel="No temperature class"/> <s:convertEnum /> </h:selectOneMenu> </td> </tr> <tr> <td>#{messages['chemical.dangers']}</td> <td> <h:selectManyCheckbox value="#{chemical.dangers}" layout="pageDirection"> <s:selectItems value="#{enumLists.dangers}" var="danger" label="#{danger.code}"/> <s:convertEnum /> </h:selectManyCheckbox> </td> </tr> <tr> <td>#{messages['chemical.requiredEquipment']}</td> <td> <h:selectManyCheckbox value="#{chemical.equipment}"> <s:selectItems value="#{enumLists.equipment}" var="item" label="#{item}"/> <s:convertEnum /> </h:selectManyCheckbox> </td> </tr> <tr> <td>#{messages['chemical.densityAt20C']}</td> <td> <s:decorate id="chemicalDensity" template="/layout/edit.xhtml"> <h:inputText value="#{chemical.density}"> <a:support event="onblur" reRender="chemicalDensity" bypassUpdates="true"/> </h:inputText> </s:decorate> </td> </tr> <tr> <td>#{messages['chemical.nrBlueLights']}</td> <td> <s:decorate id="chemicalBlueLights" template="/layout/edit.xhtml"> <h:inputText value="#{chemical.bluelights}"> <a:support event="onblur" reRender="chemicalBlueLights" bypassUpdates="true"/> </h:inputText> </s:decorate> </td> </tr> <tr> <td>#{messages['chemical.allowUnderdeckPumproom']}</td> <td><h:selectBooleanCheckbox value="#{chemical.allowPumpRoom}" /></td> </tr> <tr> <td>#{messages['chemical.samplingDevice']}</td> <td> <h:selectOneMenu value="#{chemical.samplingDevice}"> <s:selectItems value="#{enumLists.samplingDevices}" var="dev" label="#{messages[dev.key]}" /> <s:convertEnum /> </h:selectOneMenu> </td> </tr> <tr> <td>#{messages['chemical.tankerType']}</td> <td> <h:selectOneMenu value="#{chemical.tankType}"> <s:selectItems value="#{enumLists.tankTypes}" var="type" label="#{type}" /> <s:convertEnum /> </h:selectOneMenu> </td> </tr> <tr> <td>#{messages['chemical.tankCondition']}</td> <td> <h:selectOneMenu value="#{chemical.tankCondition}"> <s:selectItems value="#{enumLists.conditions}" var="c" label="#{messages[c.key]}" /> <s:convertEnum /> </h:selectOneMenu> </td> </tr> <tr> <td>#{messages['chemical.hullType']}</td> <td> <h:selectOneMenu value="#{chemical.hullType}"> <s:selectItems value="#{enumLists.hullTypes}" var="dev" label="#{messages[dev.key]}" /> <s:convertEnum /> </h:selectOneMenu> </td> </tr> <tr> <td>#{messages['chemical.tankEquipment']}</td> <td> <h:selectOneMenu value="#{chemical.tankEquipment}"> <s:selectItems value="#{enumLists.tankEquipment}" var="e" label="#{messages[e.key]}" noSelectionLabel="Not Available" /> <s:convertEnum /> </h:selectOneMenu> </td> </tr> <tr> <td>#{messages['chemical.relievePressure']}</td> <td> <s:decorate id="chemicalRelievePressure" template="/layout/edit.xhtml"> <h:inputText value="#{chemical.relievePressure}"> <a:support event="onblur" reRender="chemicalRelievePressure" bypassUpdates="true"/> </h:inputText> </s:decorate> </td> </tr> <tr> <td>#{messages['chemical.maxFillRate']}</td> <td> <s:decorate id="chemicalMaxFillRate" template="/layout/edit.xhtml"> <h:inputText value="#{chemical.maxFillRate}"> <a:support event="onblur" reRender="chemicalMaxFillRate" bypassUpdates="true"/> </h:inputText> </s:decorate> </td> </tr> </tbody> </table> </rich:tab> <rich:tab label="#{messages['chemical.compatibility']}"> <table cellpadding="0" cellspacing="1" border="0" class="properties"> <tbody> <ui:repeat value="#{chemical.compatibility}" var="c"> <tr> <td>#{c.material.symbol}</td> <td> <h:selectOneMenu value="#{c.compatibilityStatus}"> <s:selectItems value="#{enumLists.status}" var="z" label="#{messages[z.key]}" /> <s:convertEnum /> </h:selectOneMenu> </td> </tr> </ui:repeat> </tbody> </table> </rich:tab> <rich:tab label="#{messages['chemical.notes']}"> <table cellpadding="0" cellspacing="1" border="0" class="properties"> <tbody> <ui:repeat value="#{chemical.notes}" var="note"> <tr> <td>xx: #{note.number}</td> </tr> </ui:repeat> </tbody> </table> </rich:tab> <rich:tab label="#{messages['chemical.internalNotes']}"> <h:inputTextarea value="#{chemical.internalNote}" cols="100" rows="20"/> </rich:tab> </rich:tabPanel> </h:form> </ui:composition>
This is the SessionBean:
@Stateful @Scope(SESSION) @Name("chemicalList") @Restrict("#{identity.loggedIn}") public class ChemicalListAction implements ChemicalList, Serializable { private static final long serialVersionUID = 1L; @Logger private Log log; @In EntityManager entityManager; @DataModel private List<Chemical> chemicals; @DataModelSelection private Chemical chemical; @Override public Chemical getChemical() { return chemical; } @Override @SuppressWarnings("unchecked") @Factory @Observer("chemicalsUpdated") public void getChemicals() { log.info("ChemicalList.getChemicals() called"); chemicals = entityManager.createQuery("from Chemical c order by c.un") .getResultList(); log.info("Finished retrieving chemicals"); } @Override @Destroy @Remove public void destroy() { } }
The ConversationBean:
package nl.chainsoftware.chemix.session; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; import javax.ejb.Remove; import javax.ejb.Stateful; import javax.faces.context.FacesContext; import javax.persistence.EntityManager; import nl.chainsoftware.chemix.model.Chemical; import nl.chainsoftware.chemix.model.Compatibility; import nl.chainsoftware.chemix.model.Material; import nl.chainsoftware.chemix.model.Translation; import nl.chainsoftware.chemix.model.User; import org.jboss.seam.annotations.Begin; import org.jboss.seam.annotations.End; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Logger; import org.jboss.seam.annotations.Out; import org.jboss.seam.annotations.security.Restrict; import org.jboss.seam.core.Events; import org.jboss.seam.annotations.Factory; import org.jboss.seam.log.Log; import org.jboss.seam.faces.FacesMessages; @Stateful @Name("chemicalManager") @Restrict("#{identity.loggedIn}") public class ChemicalManagerBean implements ChemicalManager { @Logger private Log log; @In FacesMessages facesMessages; @In private EntityManager entityManager; @In(required=false) @Out private Chemical chemical; @In private Events events; @In private User currentUser; public void select(Chemical selectedChemical) { log.info("ChemicalManager.select() called with chemical id: " + selectedChemical.getId()); chemical = selectedChemical; log.info("ChemicalManager.select() exited with chemical id: " + selectedChemical.getId()); } public void persist() { log.info("Saving new chemical"); chemical.setUpdated(new Date()); chemical.setUpdatedby(currentUser); entityManager.persist(chemical); // TODO: raise transaction success event } public void remove() { log.info("chemicalManager.remove() called with chemical id: " + chemical.getId()); /* This is necessary otherwise a remove fails with "deleted entity passed to persist" * Should be moved to one (material, chemical or compatibility) of the entities */ List<Compatibility> comps = chemical.getCompatibility(); for (Compatibility c : comps) { Material m = c.getMaterial(); m.getCompatibilities().remove(c); } entityManager.remove(chemical); entityManager.flush(); createChemical(); events.raiseTransactionSuccessEvent("chemicalsUpdated"); } public List<Locale> getMissingLanguages() { if (chemical == null) return null; List<Locale> lang = new ArrayList<Locale>(); Iterator<Locale> locales = FacesContext.getCurrentInstance().getApplication().getSupportedLocales(); while ( locales.hasNext() ) { Locale locale = locales.next(); if (!hasLanguage(locale.getLanguage())) lang.add(locale); } return lang; } public boolean isManaged() { if (chemical == null) return false; if (!entityManager.contains(chemical)) return false; return true; } @Remove public void destroy() {} private boolean hasLanguage(String language) { if (chemical.getTranslations() == null) return false; for (Translation t : chemical.getTranslations()) { if (t.getLanguage().equals(language)) return true; } return false; } @Override public void build() { // TODO Auto-generated method stub } @Override public void update() { log.info("Saving chemical " + chemical.getId()); try { entityManager.persist(chemical); facesMessages.add("Chemical has been saved"); log.info("Chemical saved"); } catch (Exception e) { log.fatal("Could not save chemical (" + chemical.getId() + ") " + e); facesMessages.add("Something went wrong. Your chemical has NOT been saved"); revert(); } } private void revert() { chemical = entityManager.find(Chemical.class, chemical.getId()); } @Factory("chemical") @SuppressWarnings("unchecked") public void createChemical() { log.info("Creating new chemical"); chemical = new Chemical(); List<Compatibility> compatibilityList = new ArrayList<Compatibility>(); List<Material> materials = entityManager.createQuery("from Material").getResultList(); for (Material material : materials) { Compatibility c = new Compatibility(chemical, material); compatibilityList.add(c); } chemical.setCompatibility(compatibilityList); } }
and the Entity itself:
@Entity @Name("chemical") public class Chemical implements Serializable { private static final long serialVersionUID = -81501463360195381L; private int id; private int un; private int maxFillRate; private boolean allowPumpRoom; private TemperatureClass temperatureClass; private boolean explosionProtection; private double density; private String classification; private int relievePressure; private Date updated; private User updatedby; private String classificationCode; private String packageGroup; private int bluelights; private List<ChemicalNoteConnector> notes; private List<Compatibility> compatibility; private List<Danger> dangers; private List<Equipment> equipment; private SamplingDevice samplingDevice; private List<Translation> translations; private TankType tankType; private HullType hullType; private ExplosionGroup explosionGroup; private TankEquipment tankEquipment; private Condition tankCondition; private String internalNote; @Id @GeneratedValue(strategy = GenerationType.AUTO) public int getId() { return id; } public void setId(int id) { this.id = id; } @Min(value=1) public int getUn() { return un; } public void setUn(int un) { this.un = un; } @Range(min=0, max=100) public int getMaxFillRate() { return maxFillRate; } public void setMaxFillRate(int maxFillRate) { this.maxFillRate = maxFillRate; } public boolean isAllowPumpRoom() { return allowPumpRoom; } public void setAllowPumpRoom(boolean allowPumpRoom) { this.allowPumpRoom = allowPumpRoom; } public TemperatureClass getTemperatureClass() { return temperatureClass; } public void setTemperatureClass(TemperatureClass temperatureClass) { this.temperatureClass = temperatureClass; } public boolean isExplosionProtection() { return explosionProtection; } public void setExplosionProtection(boolean explosionProtection) { this.explosionProtection = explosionProtection; } @Min(value=0) public double getDensity() { return density; } public void setDensity(double density) { this.density = density; } public String getClassification() { return classification; } public void setClassification(String classification) { this.classification = classification; } @Min(value=0) public int getRelievePressure() { return relievePressure; } public void setRelievePressure(int relievePressure) { this.relievePressure = relievePressure; } public Date getUpdated() { return updated; } public void setUpdated(Date updated) { this.updated = updated; } @ManyToOne @NotNull public User getUpdatedby() { return updatedby; } public void setUpdatedby(User updatedby) { this.updatedby = updatedby; } public String getClassificationCode() { return classificationCode; } public void setClassificationCode(String classificationCode) { this.classificationCode = classificationCode; } public String getPackageGroup() { return packageGroup; } public void setPackageGroup(String packageGroup) { this.packageGroup = packageGroup; } @Min(value=0) public int getBluelights() { return bluelights; } public void setBluelights(int bluelights) { this.bluelights = bluelights; } @OneToMany(mappedBy="chemical") public List<ChemicalNoteConnector> getNotes() { return notes; } public void setNotes(List<ChemicalNoteConnector> notes) { this.notes = notes; } @OneToMany(mappedBy="chemical", cascade=CascadeType.ALL) public List<Compatibility> getCompatibility() { return compatibility; } public void setCompatibility(List<Compatibility> compatibility) { this.compatibility = compatibility; } @CollectionOfElements public List<Danger> getDangers() { return dangers; } public void setDangers(List<Danger> dangers) { this.dangers = dangers; } @CollectionOfElements public List<Equipment> getEquipment() { return equipment; } public void setEquipment(List<Equipment> equipment) { this.equipment = equipment; } @NotNull public SamplingDevice getSamplingDevice() { return samplingDevice; } public void setSamplingDevice(SamplingDevice samplingDevice) { this.samplingDevice = samplingDevice; } @OneToMany(mappedBy="chemical", fetch=FetchType.EAGER, cascade=CascadeType.ALL) public List<Translation> getTranslations() { return translations; } public void setTranslations(List<Translation> translations) { this.translations = translations; } @NotNull public TankType getTankType() { return tankType; } public void setTankType(TankType tankType) { this.tankType = tankType; } @NotNull public HullType getHullType() { return hullType; } public void setHullType(HullType hullType) { this.hullType = hullType; } public ExplosionGroup getExplosionGroup() { return explosionGroup; } public void setExplosionGroup(ExplosionGroup explosionGroup) { this.explosionGroup = explosionGroup; } @NotNull public Condition getTankCondition() { return tankCondition; } public void setTankCondition(Condition tankCondition) { this.tankCondition = tankCondition; } public TankEquipment getTankEquipment() { return tankEquipment; } public void setTankEquipment(TankEquipment tankEquipment) { this.tankEquipment = tankEquipment; } @Column(length=5000) public String getInternalNote() { return internalNote; } public void setInternalNote(String internalNote) { this.internalNote = internalNote; } }