Key is to avoid referring to the Master's mapped list of Details (or remove the Hibernate mapping entirely?). This will mean that Hibernate will not load the large number of Details. However, transaction management of the Detail instances becomes a problem, as I struggle to find a solution that retains Seam's transaction management, but has it including new Details as well as changed items.
The solution that follows does function. The normal editing of existing Details works as expected, with changes saved when the Save button is pushed. However any new Detail instances are saved immediately on closing the ModalPanel. This problem is because we're not adding the new Details into the Master (which would mean that they are persisted as part of saving the Master).
One solution would be to keep a local List of new Detail instances, and override the EntityHome update() and persist() functions to iterate the List and persist() the Detail entities. However going to this much trouble is making me wonder if there is not a simpler approach.
Anyway, here is the code for the revised example:
1. The EntityHome class:
package org.asr.md.session;
import javax.persistence.EntityManager;
import org.asr.md.entity.*;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.framework.EntityHome;
@Name("masterEditorWithDetailPaging")
@Scope(ScopeType.CONVERSATION)
public class MasterEditorWithDetailPaging extends EntityHome{
private static final long serialVersionUID = -7731494569738456413L;
@In EntityManager entityManager;
@In(create=true) DetailListForPaging detailListForPaging;
private String filterDescription;
private Detail currentlySelectedDetail;
//Getters and setters
public String getFilterDescription() { return this.filterDescription; }
public void setFilterDescription(String filterDescription) { this.filterDescription = filterDescription; }
public Detail getCurrentlySelectedDetail() { return this.currentlySelectedDetail; }
public void selectDetail(Detail detail) { this.currentlySelectedDetail = detail; }
public void createNewDetail() {
Detail detail = new Detail();
detail.setMaster(getInstance());
detailListForPaging.getResultList().add(detail);
currentlySelectedDetail = detail;
}
public void saveNewDetail() {
if (!entityManager.contains(currentlySelectedDetail)) {
entityManager.persist(currentlySelectedDetail);
}
}
public void cancelNewDetail() {
removeDetail(currentlySelectedDetail);
}
public void cancelChanges() {
entityManager.clear();
}
//Delete a detail item
public void removeDetail(Detail detail) {
detailListForPaging.getResultList().remove(detail);
entityManager.remove(detail);
}
public String filterDetails() {
detailListForPaging.setMasterInstance(getInstance());
detailListForPaging.setDetailDescription(filterDescription);
return "success";
}
public void setMasterId(Integer id) {
setId(id);
}
public Integer getMasterId() {
return (Integer) getId();
}
@Override
public String update() {
return super.update();
}
@Override
protected Master createInstance() {
Master master = new Master();
return master;
}
public void wire() {
getInstance();
}
public boolean isWired() {
return true;
}
public Master getDefinedInstance() {
return isIdDefined() ? getInstance() : null;
}
}
2. The DetailQuery class:
package org.asr.md.session;
import org.asr.md.entity.*;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.framework.EntityQuery;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
@Name("detailListForPaging")
@Scope(ScopeType.CONVERSATION)
public class DetailListForPaging extends EntityQuery{
private static final long serialVersionUID = -427189818462003108L;
private static final String EJBQL = "select detail from Detail detail";
private static final String[] RESTRICTIONS = { "detail.master = #{detailListForPaging.masterInstance}",
"lower(detail.detailDescription) like concat(#{detailListForPaging.detailDescription},'%')", };
private String detailDescription = new String();
private Master master;
private boolean criteriaHaveChanged = true;
public DetailListForPaging() {
setEjbql(EJBQL);
setRestrictionExpressionStrings(Arrays.asList(RESTRICTIONS));
setMaxResults(25);
}
public ListgetResultList() {
//If the detailDescription is not set to something, then return an empty List.
if ((master == null) || (detailDescription.isEmpty())) {
return new Vector(0);
}
if (criteriaHaveChanged) {
setRestrictionExpressionStrings(Arrays.asList(RESTRICTIONS));
criteriaHaveChanged = false;
}
return super.getResultList();
}
public void setDetailDescription(String detailDescription) {
this.detailDescription = detailDescription;
this.criteriaHaveChanged = true;
}
public String getDetailDescription() { return this.detailDescription; }
public void setMasterInstance(Master master) {
this.master = master;
this.criteriaHaveChanged = true;
}
public Master getMasterInstance() { return this.master; }
}
3. The XHTML page:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<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:rich="http://richfaces.org/rich"
template="layout/template.xhtml">
<ui:define name="body">
<rich:modalPanel id="detailEditWizard"
minHeight="200"
minWidth="500"
autosized="true">
<f:facet name="header"><h:outputText value="Edit Detail..." /></f:facet>
<f:facet name="controls">
<h:graphicImage value="/img/ico_close.gif"
styleClass="linkImage"
onclick="#{rich:component('detailEditWizard')}.hide()" />
</f:facet>
<h:form id="detailEditForm" styleClass="edit">
<h:messages globalOnly="true"/>
<s:decorate id="masterIdField" template="layout/edit.xhtml">
<ui:define name="label">Master id</ui:define>
<h:outputText id="masterId"
required="true"
value="#{masterEditorWithDetailPaging.currentlySelectedDetail.master.id}">
</h:outputText>
</s:decorate>
<s:decorate id="detailDescriptionField" template="layout/edit.xhtml">
<ui:define name="label">Detail description</ui:define>
<h:inputText id="detailDescription"
required="true"
size="100"
maxlength="100"
value="#{masterEditorWithDetailPaging.currentlySelectedDetail.detailDescription}">
<a:support event="onblur" reRender="detailDescriptionField" bypassUpdates="true" ajaxSingle="true"/>
</h:inputText>
</s:decorate>
<div class="actionButtons" style="clear:both">
<a:commandButton id="detailEditMPClose"
onclick="#{rich:component('detailEditWizard')}.hide()"
action="#{masterEditorWithDetailPaging.saveNewDetail()}"
reRender="detailListTable"
value="Save" />
<a:commandButton id="detailEditMPCancel"
onclick="#{rich:component('detailEditWizard')}.hide()"
action="#{masterEditorWithDetailPaging.cancelNewDetail()}"
reRender="detailListTable"
value="Cancel" />
</div>
</h:form>
</rich:modalPanel>
<h:form id="master" styleClass="edit">
<rich:panel>
<f:facet name="header">#{masterEditorWithDetailPaging.managed ? 'Edit' : 'Add'} Master</f:facet>
<s:decorate id="masterDescriptionField" template="layout/edit.xhtml">
<ui:define name="label">Master description</ui:define>
<h:inputText id="masterDescription"
size="100"
maxlength="100"
value="#{masterEditorWithDetailPaging.instance.masterDescription}">
<a:support event="onblur" reRender="masterDescriptionField" bypassUpdates="true" ajaxSingle="true"/>
</h:inputText>
</s:decorate>
<div style="clear:both">
<span class="required">*</span>
required fields
</div>
</rich:panel>
<rich:panel>
<f:facet name="header">Detail Filter</f:facet>
<s:decorate id="detailDescriptionFilterField" template="layout/edit.xhtml">
<ui:define name="label">Detail Description</ui:define>
<h:inputText id="detailDescriptionFilter"
size="100"
maxlength="100"
value="#{masterEditorWithDetailPaging.filterDescription}">
<a:support event="onblur" reRender="detailDescriptionFilterField" bypassUpdates="true" ajaxSingle="true"/>
</h:inputText>
</s:decorate>
<div class="actionButtons">
<h:commandButton id="filter"
value="Search"
action="#{masterEditorWithDetailPaging.filterDetails}"
disabled="#{!masterEditorWithDetailPaging.wired}"
rendered="#{masterEditorWithDetailPaging.managed}">
<s:conversationId />
</h:commandButton>
<a:commandButton id="doFiltering"
action="#{masterEditorWithDetailPaging.filterDetails()}"
reRender="detailListTable"
value="Filter">
<s:conversationId />
</a:commandButton>
</div>
</rich:panel>
<!-- Detail section -->
<rich:panel>
<f:facet name="header">Detail List</f:facet>
<div class="results" id="detailList">
<h:outputText value="The detail search returned no results."
rendered="#{empty detailListForPaging.resultList}"/>
<rich:dataTable id="detailListTable"
var="_detail"
value="#{detailListForPaging.resultList}">
<h:column>
<f:facet name="header">
<ui:include src="layout/sort.xhtml">
<ui:param name="propertyLabel" value="Id"/>
</ui:include>
</f:facet>
<h:outputText value="#{_detail.id}"/>
</h:column>
<h:column>
<f:facet name="header">
<ui:include src="layout/sort.xhtml">
<ui:param name="propertyLabel" value="Master id"/>
</ui:include>
</f:facet>
<h:outputText value="#{_detail.master.id}"/>
</h:column>
<h:column>
<f:facet name="header">
<ui:include src="layout/sort.xhtml">
<ui:param name="propertyLabel" value="Detail description"/>
</ui:include>
</f:facet>
<h:outputText value="#{_detail.detailDescription}"/>
</h:column>
<rich:column styleClass="action">
<f:facet name="header">Action</f:facet>
<a:commandLink id="editDetailMP"
action="#{masterEditorWithDetailPaging.selectDetail(_detail)}"
oncomplete="javascript:Richfaces.showModalPanel('detailEditWizard')"
reRender="detailEditForm"
value="Edit">
<s:conversationId />
</a:commandLink>
#{' '}
<a:commandLink id="removeDetail"
action="#{masterEditorWithDetailPaging.removeDetail(_detail)}"
reRender="detailListTable"
value="Delete">
<s:conversationId />
</a:commandLink>
</rich:column>
</rich:dataTable>
</div>
<s:div styleClass="actionButtons" rendered="#{empty from}">
<a:commandLink id="editDetailMP"
action="#{masterEditorWithDetailPaging.createNewDetail()}"
oncomplete="javascript:Richfaces.showModalPanel('detailEditWizard')"
reRender="detailEditForm"
value="New Detail...">
<s:conversationId />
</a:commandLink>
</s:div>
<div class="tableControl">
<s:link id="firstPage"
action="#{detailListForPaging.first()}"
reRender="detailListTable"
value="First Page">
</s:link>
<s:link id="previousPage"
action="#{detailListForPaging.previous()}"
reRender="detailListTable"
value="Previous Page">
</s:link>
<s:link id="nextPage"
action="#{detailListForPaging.next()}"
reRender="detailListTable"
value="Next Page">
</s:link>
<s:link id="lastPage"
action="#{detailListForPaging.last()}"
reRender="detailListTable"
value="Last Page">
</s:link>
</div>
</rich:panel>
<div class="actionButtons">
<h:commandButton id="save"
value="Save"
action="#{masterEditorWithDetailPaging.persist}"
disabled="#{!masterEditorWithDetailPaging.wired}"
rendered="#{!masterEditorWithDetailPaging.managed}"/>
<h:commandButton id="update"
value="Save"
action="#{masterEditorWithDetailPaging.update}"
rendered="#{masterEditorWithDetailPaging.managed}"/>
<h:commandButton id="delete"
value="Delete"
action="#{masterEditorWithDetailPaging.remove}"
immediate="true"
rendered="#{masterEditorWithDetailPaging.managed}"/>
<s:button id="done"
value="Done"
propagation="end"
view="/MasterList.xhtml"
rendered="#{masterEditorWithDetailPaging.managed}"/>
<s:button id="cancel"
value="Cancel"
action="#{masterEditorWithDetailPaging.cancelChanges}"
propagation="end"
view="/#{empty masterFrom ? 'MasterList' : masterFrom}.xhtml"
rendered="#{masterEditorWithDetailPaging.managed}"/>
</div>
</h:form>
</ui:define>
</ui:composition>
4. The page.xml:
<?xml version="1.0" encoding="UTF-8"?>
<page xmlns="http://jboss.com/products/seam/pages"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd"
no-conversation-view-id="/MasterList.xhtml"
login-required="true">
<begin-conversation join="true" flush-mode="MANUAL"/>
<action execute="#{masterEditorWithDetailPaging.wire}" on-postback="false"/>
<param name="masterFrom"/>
<param name="masterId" value="#{masterEditorWithDetailPaging.masterId}"/>
<navigation from-action="#{masterEditorWithDetailPaging.persist}">
<rule>
<end-conversation/>
<redirect view-id="/MasterEditorWithPaging.xhtml"/>
</rule>
</navigation>
<navigation from-action="#{masterEditorWithDetailPaging.update}">
<rule>
<end-conversation/>
<redirect view-id="/MasterEditorWithPaging.xhtml"/>
</rule>
</navigation>
<navigation from-action="#{masterEditorWithDetailPaging.remove}">
<rule>
<end-conversation/>
<redirect view-id="/MasterEditorWithPaging.xhtml"/>
</rule>
</navigation>
</page>