对于产品类别,结构如下:
产品类别(顶级节点:root,类型:product_type):
|-娱乐(产品大类1:product_type1,类型:product_type)
|-音乐(产品分类11,product_type11)
|-流行音乐(子分类111,product_type111)
|-摇滚音乐(子分类112,product_type112)
|-产品分类12
|-商务(产品大类2)
鉴于此,在项目中,对于这样的需求的实现方法如下:
对于一种类别(例如对于产品类别product_type)的子类别在oss_category(类别表)中存储方案如下:
1、在oss_category表中插入一条记录用于标识“产品类别”对应的顶级节点,其
category_id=1,parent_id=-1,category_type=”product_type”,category_name=”root”
2、在oss_category表中插入一条记录用于标识“product_type11”2级节点,其
category_id=2,parent_id=1,category_type=”product_type”,category_name=”product_type1”
3、在oss_category表中插入一条记录用于标识“product_type11”3级节点,其
category_id=3,parent_id=2,category_type=”product_type”,category_name=”product_type11”
整个数据库脚本如下:
CREATE TABLE `oss_category` (
`category_id` int(11) NOT NULL auto_increment,
`parent_id` int(11) default ‘-1′,
`level` smallint(6) default NULL,
`is_leaf` tinyint(1) default NULL,
`category_title` varchar(100) default NULL,
`category_name` varchar(100) default NULL,
`category_code` varchar(100) default NULL,
`category_type` varchar(30) default NULL,
`image` varchar(255) default NULL,
`status` varchar(20) default NULL,
`creator` varchar(50) default NULL,
`create_date` datetime default NULL,
`modify_user` varchar(50) default NULL,
`modify_date` datetime default NULL,
`description` text,
PRIMARY KEY(`category_id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=gbk;
INSERT INTO `oss_category` VALUES (’1′, ‘-1′, ’1′, ’0′, ‘root’, ‘root’, ‘root’, ‘product_type’, null, ’1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (’2′, ’1′, ’2′, ’0′, ‘product_type1’, ‘product_type1′, ‘product_type1′, ‘product_type’, null, ’1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (’3′, ’1′, ’2′, ’0′, ‘product_typ’, ‘product_type2′, ‘product_type2′, ‘product_type’, null, ’1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (’4′, ’3′, ’3′, ’0′, ‘product_type21’, ‘product_type21′, ‘product_type21′, ‘product_type’, null, ’1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (’5′, ’2′, ’3′, ’0′, ‘product_type11’, ‘product_type11′, ‘product_type11′, ‘product_type’, null, ’1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (’6′, ’5′, ’4′, ’1′, ‘product_type111’, ‘product_type111′, ‘product_type111′, ‘product_type’, null, ’1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (’7′, ’3′, ’2′, ’0′, ‘product_type22’, ‘product_type22′, ‘product_type22′, ‘product_type’, null, ’1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (’8′, ’2′, ’3′, ’0′, ‘product_type12’, ‘product_type12′, ‘product_type12′, ‘product_type’, null, ’1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (’9′, ’4′, ’4′, ’1′, ‘product_type211’, ‘product_type211′, ‘product_type211′, ‘product_type’, null, ’1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (’10′, ’7′, ’4′, ’1′, ‘product_type221’, ‘product_type221′, ‘product_type221′, ‘product_type’, null, ’1′, null, null, null, null, null);
为了保证跨浏览器的兼容性,不采用任何控件来实现在下拉框中展现树形结构,而采用最为原始的拼凑出如下形式的select 框
也即用 来实现缩进来达到树形结构的效果,在实现上只需要在程序中拼凑出这样的字符串到界面展现即可。
在Struts2中,采用标签展现结果时候,注意property的选项escape=”false”,让Struts2不要对字符串进行转义。
代码基本上沿用了原来的树形结构的代码,由于原来与OssCategory及ossCategoryDAO存在冲突,拷贝OssCategory.hbm.xml为Tree.hbm.xml。
package com.mobilesoft.framework.tree.model;
import java.util.Date;
import java.util.List;
import java.util.Set;
import com.mobilesoft.framework.common.model.BaseObject;
/**
* OssCategory entity.
*
* @author MyEclipse Persistence Tools
*/
public class Tree extends BaseObject
implements java.io.Serializable {
// Fields
private Integer categoryId;
private Integer parentId;
private Short level;
private Byte isLeaf;
private String categoryTitle;
private String categoryName;
private String categoryCode;
private String categoryType;
private String image;
private String status;
private String creator;
private Date createDate;
private String modifyUser;
private Date modifyDate;
private String description;
private Tree[] childCategories;
// Constructors
/** default constructor */
public Tree() {
}
/** full constructor */
public Tree(Integer parentId, Short level, Byte isLeaf,
String categoryTitle, String categoryName, String categoryCode,
String categoryType, String image, String status, String creator,
Date createDate, String modifyUser, Date modifyDate,
String description) {
this.parentId = parentId;
this.level = level;
this.isLeaf = isLeaf;
this.categoryTitle = categoryTitle;
this.categoryName = categoryName;
this.categoryCode = categoryCode;
this.categoryType = categoryType;
this.image = image;
this.status = status;
this.creator = creator;
this.createDate = createDate;
this.modifyUser = modifyUser;
this.modifyDate = modifyDate;
this.description = description;
}
// Property accessors
public Integer getCategoryId() {
return this.categoryId;
}
public void setCategoryId(Integer categoryId) {
this.categoryId = categoryId;
}
public Integer getParentId() {
return this.parentId;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
public Short getLevel() {
return this.level;
}
public void setLevel(Short level) {
this.level = level;
}
public Byte getIsLeaf() {
return this.isLeaf;
}
public void setIsLeaf(Byte isLeaf) {
this.isLeaf = isLeaf;
}
public String getCategoryTitle() {
return this.categoryTitle;
}
public void setCategoryTitle(String categoryTitle) {
this.categoryTitle = categoryTitle;
}
public String getCategoryName() {
return this.categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public String getCategoryCode() {
return this.categoryCode;
}
public void setCategoryCode(String categoryCode) {
this.categoryCode = categoryCode;
}
public String getCategoryType() {
return this.categoryType;
}
public void setCategoryType(String categoryType) {
this.categoryType = categoryType;
}
public String getImage() {
return this.image;
}
public void setImage(String image) {
this.image = image;
}
public String getStatus() {
return this.status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCreator() {
return this.creator;
}
public void setCreator(String creator) {
this.creator = creator;
}
public Date getCreateDate() {
return this.createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public String getModifyUser() {
return this.modifyUser;
}
public void setModifyUser(String modifyUser) {
this.modifyUser = modifyUser;
}
public Date getModifyDate() {
return this.modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public Tree[] getChildCategories() {
return childCategories;
}
public void setChildCategories(Tree[] childCategories) {
this.childCategories = childCategories;
}
}
package com.mobilesoft.framework.tree.dao.hibernate;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.LockMode;
import org.springframework.context.ApplicationContext;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import com.mobilesoft.framework.tree.model.Tree;
/**
* A data access object (DAO) providing persistence and search support for
* Tree entities. Transaction control of the save(), update() and
* delete() operations can directly support Spring container-managed
* transactions or they can be augmented to handle user-managed Spring
* transactions. Each of these methods provides additional information for how
* to configure it for the desired type of transaction control.
*
* @see com.mobilesoft.esales.model.Tree
* @author MyEclipse Persistence Tools
*/
public class TreeDAO extends HibernateDaoSupport {
/**
* Logger for this class
*/
private static final Logger logger = Logger.getLogger(TreeDAO.class);
private static final Log log = LogFactory.getLog(TreeDAO.class);
// property constants
public static final String PARENT_ID = “parentId”;
public static final String LEVEL = “level”;
public static final String IS_LEAF = “isLeaf”;
public static final String tree_TITLE = “categoryTitle”;
public static final String tree_NAME = “categoryName”;
public static final String tree_CODE = “categoryCode”;
public static final String tree_TYPE = “categoryType”;
public static final String IMAGE = “image”;
public static final String STATUS = “status”;
public static final String CREATOR = “creator”;
public static final String MODIFY_USER = “modifyUser”;
public static final String DESCRIPTION = “description”;
public String delimiter;
protected void initDao() {
// do nothing
}
/*
省略掉其他方法
*/
public static TreeDAO getFromApplicationContext(
ApplicationContext ctx) {
return (TreeDAO) ctx.getBean(“treeDAO”);
}
public Tree[] getAlltree() {
ArrayList
String queryString=”from Tree as tree where tree.parentId=1″;
List
Iterator iterator=queryList.iterator();
while(iterator.hasNext()){
Tree tree=(Tree)iterator.next();
Tree[] childrenArray=getChildCategoriesById(tree.getCategoryId());
logger.debug(“getAlltree() – Tree[] childrenList=” + childrenArray.length);
tree.setChildCategories(childrenArray);
resultList.add(tree);
}
Tree[] resultArray=(Tree[])resultList.toArray(new Tree[resultList.size()]);
return resultArray;
}
public String getChildrenByType(String type,boolean includeRoot,String delimiter ){
this.delimiter=delimiter;
StringBuffer resultBuffer=new StringBuffer();
resultBuffer.append(“”);
return resultBuffer.toString();
}else{
return “”;
}
}
public Tree[] getChildCategoriesById(int treeId) {
String queryString=”from Tree as tree where tree.parentId=”+treeId;
List
logger.debug(“getChildCategoriesById(int) – List
ArrayList
Iterator iterator=queryList.iterator();
while(iterator.hasNext()){
Tree tree=(Tree)iterator.next();
Tree[] childrenList=getChildCategoriesById(tree.getCategoryId());
logger.debug(“getChildCategoriesById(int) – Tree[] childrenList=” + childrenList+”parentid is “+treeId);
tree.setChildCategories(childrenList);
resultList.add(tree);
}
Tree[] resultArray=(Tree[])resultList.toArray(new Tree[resultList.size()]);
return resultArray;
}
/*
* @return 要拼凑出如下形式的select 框
*
*
* @param treeId节点的id号
* @param rootLevel所在类别子树根节点在整棵树中的层级
*/
public String getChildCategories4Select(int treeId,int rootLevel) {
StringBuffer childBuffer=new StringBuffer();
String queryString=”from Tree as tree where tree.parentId=”+treeId;
List
logger.debug(“getChildCategories4Select(int,int) – List
ArrayList
Iterator iterator=queryList.iterator();
while(iterator.hasNext()){
Tree tree=(Tree)iterator.next();
int childLevel=tree.getLevel();
childBuffer.append(“
childBuffer.append(tree.getCategoryId());
childBuffer.append(” ‘>”);
for(int i=0;i<=childLevel-rootLevel;i++)
childBuffer.append(“ ”);
childBuffer.append(this.delimiter+tree.getCategoryName());
childBuffer.append(“”);
String childrenStr=getChildCategories4Select(tree.getCategoryId(),rootLevel);
childBuffer.append(childrenStr);
logger.debug(“getChildCategories4Select(int,int) – childBuffer=” + childBuffer);
}
return childBuffer.toString();
}
}
TreeService.java
package com.mobilesoft.framework.tree.service;
import java.util.List;
import java.util.Set;
import com.mobilesoft.framework.tree.model.Tree;
public interface TreeService {
/**
* @return 获取下级子节点
*/
//public Set getChildren(int rootId);
/**
* @return 递归指定级别的所有子节点
*
*/
//public Set getChildrenByLevel(int rootId,int level);
/**
* 用于获取数据字典中指定类别的所有类型的节点,
* 类别表节点的树形结构
* |– ROOT 根类别(-1)
* |–类别类型:套餐类别 bundle_type
* |–3 套餐类别1
* |–4 套餐类别2
* |–类别类型:产品类别 product_type
* |–产品大类1
* |–产品大类11
* |—产品大类别111
* |—产品大类别112
* |–产品大类12
* @return 获取指定类型的所有子节点
* 在实现上,对于诸如”产品类别“等下拉框可能为树形结构的元素,
* 为了避免由于浏览器兼容性问题,在形式上是树形结构,
* 实际上就是从字典表(目前树形结构仍然采用oss_category作为字典表)动态取出数据,然后拼凑成字符串形式展现,例如:
*
* @param type:节点的类型
* @param levle:节点相对于所属类型的节点的级别 -1表示获取所属类型的根节点的所有级别的子节点
*/
public String getChildrenByType( String type);
public String getChildrenByType( String type,boolean includeRoot);
public String getChildrenByType( String type,boolean includeRoot,String delimiter);
/**
* @return 获取指定根节点的所有子节点
*/
public Tree[]getAllTree();
}
TreeServiceImpl.java
package com.mobilesoft.framework.tree.service.impl;
import java.util.List;
import java.util.Set;
import com.mobilesoft.framework.tree.dao.hibernate.TreeDAO;
import com.mobilesoft.framework.tree.model.Tree;
import com.mobilesoft.framework.tree.service.TreeService;
public class TreeServiceImpl implements TreeService {
TreeDAO treeDAO;
String delimiter;
public Set getChildren(int rootId) {
// TODO Auto-generated method stub
return null;
}
public Set getLevelChildren(int rootId, int level) {
// TODO Auto-generated method stub
return null;
}
public String getChildrenByType( String type){
return treeDAO.getChildrenByType(type,true,this.delimiter);
}
public String getChildrenByType( String type,boolean includeRoot){
return treeDAO.getChildrenByType(type,includeRoot,this.delimiter);
}
public String getChildrenByType( String type,boolean includeRoot,String delimiter){
return treeDAO.getChildrenByType(type,includeRoot,delimiter);
}
public Tree[]getAllTree(){
return treeDAO.getAlltree();
}
public TreeDAO gettreeDAO() {
return treeDAO;
}
public void settreeDAO(TreeDAO treeDAO) {
this.treeDAO = treeDAO;
}
public String getDelimiter() {
return delimiter;
}
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
}
以Action中为例:
private TreeService treeService;
public String getChildrenByType(){
String selectResult=treeService.getChildrenByType(“product_type”);
getRequest().setAttribute(“selectResult”, selectResult);
returnSUCCESS;
}
public TreeService getTreeService() {
returntreeService;
}
publicvoid setTreeService(TreeService treeService) {
this.treeService = treeService;
}
注意:getChildrenByType方法有三个重载(overload)方法,
public String getChildrenByType( String type);
public String getChildrenByType( String type,boolean includeRoot);
public String getChildrenByType( String type,boolean includeRoot,String delimiter);
但实际上最后还是调用的是getChildrenByType( String type,boolean includeRoot,String delimiter),
参数说明:
type: 类别的类型,例如product_type
includeRoot:是否包含所在类别的根节点,例如查询product_type是否,是否需要导出所在类别的root节点
delimiter:在下拉框选择时候,分隔符类型,缺省为“|-”,可以在applicationContext-service.xml中修改或调用时候传递参数。
applicationContext-resources.xml
applicationContext-dao.xml
class=”com.mobilesoft.framework.tree.dao.hibernate.TreeDAO”>
applicationContext-service.xml
class=”com.mobilesoft.framework.tree.service.impl.TreeServiceImpl”>
action-servlet.xml
class=”com.mobilesoft.esales.webapp.action.TreeAction”>
struts.xml