Please help an EJB3-Starter with the following problem.
I have started experimenting a little with the relationships of EJB3 and created a CatalogCategory class, which allows for tree-browsing of a catalog. One category can have several sub-categories and so forth. When using fetchtype EAGER all works fine, but unfortunately the complete catalog category tree is loaded the first time I access the root element. fetch.type LAZY seems to solve this problem, as only those categories will be loaded that i really want to browse through.
I am now having problems implementing this LAZY fetchtype, because I get the following error:
Quote:
failed to lazily initialize a collection of role: dj.ecatalog.entity.CatalogCategoryBean.children, no session or session was closed
Here is my entity class:
Code:
public @Entity @Table(name="catalogcategory") class CatalogCategoryBean implements CatalogCategory {
/**
*
*/
private static final long serialVersionUID = 1L;
private int id; //UUID
private DomainBean domain; //DOMAINID
private Date lastmodified = null; //LASTMODIFIED
private String name = null; //NAME
private CatalogCategoryBean parentCategory = null; //PARENTCATEGORYID
private String properties = null; //PROPERTIES
private List<ProductTypeBean> productTypes = null;
private List<CatalogCategoryBean> children = null;
/**
* Default constructor
*/
public CatalogCategoryBean() {
super();
System.out.println("category erstellt");
}
/**
* Copy constructor
* @param original Original in copy origin
*/
public CatalogCategoryBean(CatalogCategoryBean original) {
this();
setDomain(original.getDomain());
setLastModified(original.getLastModified());
setName(original.getName());
setParentCategory(original.getParentCategory());
setProperties(original.getProperties());
}
/**
* @return the unique id
*/
public @Id(generate=GeneratorType.AUTO) int getId() {
return id;
}
/**
* @param id the id to set
*/
public void setId(int id) {
this.id = id;
}
/**
* @return the DOMAINID
*/
public @ManyToOne(optional=false) DomainBean getDomain() {
return domain;
}
/**
* @param domainid the DOMAINID to set
*/
public void setDomain(DomainBean domain) {
this.domain = domain;
}
/**
* @return the LASTMODIFIED
*/
public Date getLastModified() {
return lastmodified;
}
/**
* @param lastmodified the LASTMODIFIED to set
*/
public void setLastModified(Date lastmodified) {
this.lastmodified = lastmodified;
}
/**
* @return the NAME
*/
public @Column(length=25) String getName() {
return name;
}
/**
* @param name the NAME to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the PARENTCATEGORYID
*/
public @ManyToOne(optional=true, fetch = FetchType.LAZY) CatalogCategoryBean getParentCategory() {
return parentCategory;
}
/**
* @param parentcategoryid the PARENTCATEGORYID to set
*/
public void setParentCategory(CatalogCategoryBean parentcategory) {
this.parentCategory = parentcategory;
}
public @Transient PropertiesDocument getProperties() {
try {
return PropertiesDocument.Factory.parse(getPropertiesDocument());
} catch (Exception e) {
System.out.println("ERROR:" + e);
}
return null;
}
private @Column(columnDefinition="TEXT") String getPropertiesDocument() {
return properties;
}
private void setPropertiesDocument(String properties) {
this.properties = properties;
}
/**
* @param properties the PROPERTIES to set
*/
public void setProperties(PropertiesDocument properties) {
setPropertiesDocument(properties.toString());
}
/**
* Clone method
* @return The deep copy of this instance
*/
protected Object clone() {
return new CatalogCategoryBean(this);
}
/**
* equals method<BR>
* When the value of all items is the same, true is returned.
* @return True when value of all items is the same
*/
public boolean equals(Object obj) {
if (!(obj instanceof CatalogCategoryBean))
return false;
CatalogCategoryBean target = (CatalogCategoryBean) obj;
if (getId() != target.getId())
return false;
if (!getDomain().equals(target.getDomain()))
return false;
if (!getLastModified().equals(target.getLastModified()))
return false;
if (!getName().equals(target.getName()))
return false;
if (!getParentCategory().equals(target.getParentCategory()))
return false;
if (!getProperties().equals(target.getProperties()))
return false;
return true;
}
/**
* toString method<BR>
* The value of all items of this instance is written.
*/
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("getId=" + getId() + ":");
buf.append("getDomain=" + getDomain() + ":");
buf.append("getLastModified=" + getLastModified() + ":");
buf.append("getName=" + getName() + ":");
buf.append("getParentCategory=" + getParentCategory() + ":");
buf.append("getProperties=" + getProperties() + ":");
return buf.toString();
}
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy="catalogCategory")
public List<ProductTypeBean> getProductTypes() {
return productTypes;
}
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy="parentCategory")
public List<CatalogCategoryBean> getChildren() {
//hSessionrefresh(myClass);
return children;
}
public void setProductTypes(List<ProductTypeBean> productTypes) {
this.productTypes = productTypes;
}
public void setChildren(List<CatalogCategoryBean> children) {
this.children = children;
}
public void addProductType(ProductTypeBean productType) {
if (productTypes == null) {
productTypes = new ArrayList<ProductTypeBean>();
}
productType.setCatalogCategory(this);
productTypes.add(productType);
}
}
If I understand correctly, my bean instance, where I call getChildren from, is detached from the EntityManager's session. So how do I reattach it or keep the session from closing after I retrieve the objects?
Maybe it's worth mentioning, that I am using the
Code:
<tree2>
tag from MyFaces to display the catalog category tree and I've created a TreeModel based on my catalogcategory bean. NOTE! This problem is not a Myfaces issue, as the problem occurs with any other display technology that I have tried. I just paste the code here, so that you get an idea of what I'm trying to accomplish.
The code is as follows:
Code:
public class CatalogCategoryModel implements TreeModel {
/**
* Automatically generated serial version ID
*/
private static final long serialVersionUID = 5380255282827745941L;
private TreeNode root;
private TreeNode currentNode;
private TreeState treeState = new TreeStateBase();
public CatalogCategoryModel(List<CatalogCategory> rootCategories)
{
root = new TreeNodeBase("catalog", "Kataloge", false);
System.out.println("Hole");
Iterator<CatalogCategory> iterator = rootCategories.iterator();
System.out.println("geholt");
while (iterator.hasNext()) {
CatalogCategory category = iterator.next();
root.getChildren().add(new CatalogCategoryNode(category));
}
}
// see interface
public TreeState getTreeState()
{
return treeState;
}
// see interface
public void setTreeState(TreeState treeState)
{
this.treeState = treeState;
}
/**
* Gets the current {@link TreeNode} or <code>null</code> if no node ID is selected.
* @return The current node
*/
public TreeNode getNode()
{
return currentNode;
}
/**
* Sets the current {@link TreeNode} to the specified node ID, which is a colon-separated list
* of node indexes. For instance, "0:0:1" means "the second child node of the first child node
* under the root node."
*
* @param nodeId The id of the node to set
*/
public void setNodeId(String nodeId)
{
if (nodeId == null)
{
currentNode = null;
return;
}
currentNode = getNodeById(nodeId);
}
/**
* Gets an array of String containing the ID's of all of the {@link TreeNode}s in the path to
* the specified node. The path information will be an array of <code>String</code> objects
* representing node ID's. The array will starting with the ID of the root node and end with
* the ID of the specified node.
*
* @param nodeId The id of the node for whom the path information is needed.
* @return String[]
*/
public String[] getPathInformation(String nodeId)
{
if (nodeId == null)
{
throw new IllegalArgumentException("Cannot determine parents for a null node.");
}
ArrayList pathList = new ArrayList();
pathList.add(nodeId);
while (nodeId.lastIndexOf(SEPARATOR) != -1)
{
nodeId = nodeId.substring(0, nodeId.lastIndexOf(SEPARATOR));
pathList.add(nodeId);
}
String[] pathInfo = new String[pathList.size()];
for (int i=0; i < pathInfo.length; i++)
{
pathInfo[i] = (String)pathList.get(pathInfo.length - i - 1);
}
return pathInfo;
}
/**
* Indicates whether or not the specified {@link TreeNode} is the last child in the <code>List</code>
* of children. If the node id provided corresponds to the root node, this returns <code>true</code>.
*
* @param nodeId The ID of the node to check
* @return boolean
*/
public boolean isLastChild(String nodeId)
{
if (nodeId.lastIndexOf(SEPARATOR) == -1)
{
// root node considered to be the last child
return true;
}
//first get the id of the parent
String parentId = nodeId.substring(0, nodeId.lastIndexOf(SEPARATOR));
String childString = nodeId.substring(nodeId.lastIndexOf(SEPARATOR) + 1);
int childId = Integer.parseInt(childString);
TreeNode parentNode = getNodeById(parentId);
return childId + 1== parentNode.getChildCount();
}
private TreeNode getNodeById(String nodeId)
{
TreeNode node = root;
StringBuffer sb = new StringBuffer();
StringTokenizer st = new StringTokenizer(nodeId, SEPARATOR);
sb.append(st.nextToken()).append(SEPARATOR);
while (st.hasMoreTokens())
{
int nodeIndex = Integer.parseInt(st.nextToken());
sb.append(nodeIndex);
// don't worry about invalid index, that exception will be caught later and dealt with
node = (TreeNode)node.getChildren().get(nodeIndex);
sb.append(SEPARATOR);
}
return node;
}
}
A catalogCategory is represented as a node and the method getChildren is where the error occurs:
Code:
public class CatalogCategoryNode implements TreeNode, Comparable {
private static final long serialVersionUID = 278589014441538822L;
protected CatalogCategory category = null;
private ArrayList children = null;
private String identifier;
public CatalogCategoryNode(CatalogCategory category)
{
this.category = category;
}
public boolean isLeaf()
{
return getChildCount() == 0;
}
public void setLeaf(boolean leaf)
{
return;
}
public List getChildren()
{
if (null == children) {
children = new ArrayList();
Iterator<CatalogCategoryBean> categoryIterator = category.getChildren().iterator();
while (categoryIterator.hasNext()) {
CatalogCategory category = categoryIterator.next();
children.add(new CatalogCategoryNode(category));
}
/*
Iterator<ProductTypeBean> productTypeIterator = category.getProductTypes().iterator();
while (productTypeIterator.hasNext()) {
ProductTypeBean productType = productTypeIterator.next();
children.add(new ProductTypeNode(productType));
}
*/
}
return children;
}
public String getType()
{
return (getChildCount() == 0) ? "catalog" : "category";
}
public void setType(String type)
{
return;
}
public void setDescription(String description)
{
category.setName(description);
}
public String getDescription()
{
return category.getName();
}
public void setIdentifier(String identifier)
{
this.identifier = identifier;
}
public String getIdentifier()
{
return identifier;
}
public int getChildCount()
{
return getChildren().size();
}
public int compareTo(Object obj)
{
// branches come before leaves, after this criteria nodes are sorted alphabetically
TreeNode otherNode = (TreeNode)obj;
if (isLeaf() && !otherNode.isLeaf())
{
// leaves come after branches
return 1;
}
else if (!isLeaf() && otherNode.isLeaf())
{
// branches come before leaves
return -1;
}
else
{
// both nodes are leaves or both node are branches, so compare the descriptions
return getDescription().compareTo(otherNode.getDescription());
}
}
}
The page code looks like this:
Code:
<t:tree2 id="serverTree" value="#{CatalogHandler.treeData}" var="node"
varNodeToggler="t" clientSideToggle="false"
binding="#{CatalogHandler.tree}">
<f:facet name="catalog">
<h:panelGroup>
<t:graphicImage value="/images/yellow-folder-open.png"
rendered="#{t.nodeExpanded}" border="0" />
<t:graphicImage value="/images/yellow-folder-closed.png"
rendered="#{!t.nodeExpanded}" border="0" />
<h:outputText value="#{node.description}" styleClass="nodeFolder" />
<h:outputText value=" (#{node.childCount})" styleClass="childCount"
rendered="#{!empty node.children}" />
</h:panelGroup>
</f:facet>
<f:facet name="category">
<h:panelGroup>
<t:graphicImage value="/images/blue-folder-open.gif"
rendered="#{t.nodeExpanded}" border="0" />
<t:graphicImage value="/images/blue-folder-closed.png"
rendered="#{!t.nodeExpanded}" border="0" />
<h:outputText value="#{node.description}" styleClass="nodeFolder" />
<h:outputText value=" (#{node.childCount})" styleClass="childCount"
rendered="#{!empty node.children}" />
</h:panelGroup>
</f:facet>
<f:facet name="producttype">
<h:dataTable rowClasses="standardTable_Row1,standardTable_Row2"
headerClass="standardTable_SortHeader"
footerClass="standardTable_Footer" value="#{node.children}"
var="producttype">
<h:column>
<f:facet name="header">
<h:outputText styleClass="vertikal" value="Auswahl" />
</f:facet>
<h:selectBooleanCheckbox />
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Kategorie" />
</f:facet>
<h:outputText value="#{node.description}" />
</h:column>
<f:facet name="footer">
<h:outputText value="." />
</f:facet>
</h:dataTable>
</f:facet>
</t:tree2>
Please, anyone, help me get started with LAZY fetching or at least point me to the right docs if this has been dealt with already...