在项目中有很多地方使用了树形结构,主要有几个地方:素材和软件等软件资源表,人员权限的权限资源表、人员权限的角色,套餐及产品包,现以类别表为例,说明树形结构的数据库存储、利用Struts2 中dojo的Tree标签来完成前端展现。
1、 数据库结构
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′, ‘<input type=\’checkbox\‘ name=\’product_type1\‘ id=\’product_type1\‘ /><b>product_type1</b>’, ‘product_type1′, ‘product_type1′, ‘product_type’, null, ‘1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (‘3′, ‘1′, ‘2′, ‘0′, ‘<input type=\’checkbox\‘ name=\’product_type2\‘ id=\’product_type2\‘ class=\’treeCheckBox\‘/><b>product_typ’, ‘product_type2′, ‘product_type2′, ‘product_type’, null, ‘1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (‘4′, ‘3′, ‘3′, ‘0′, ‘<input type=\’checkbox\‘ name=\’product_type21\‘ id=\’product_type21\‘ /><b>product_type21</b>’, ‘product_type21′, ‘product_type21′, ‘product_type’, null, ‘1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (‘5′, ‘2′, ‘3′, ‘0′, ‘<input type=\’checkbox\‘ name=\’product_type11\‘ id=\’product_type11\‘ /><b>product_type11</b>’, ‘product_type11′, ‘product_type11′, ‘product_type’, null, ‘1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (‘6′, ‘5′, ‘4′, ‘1′, ‘<input type=\’checkbox\‘ name=\’product_type111\‘ id=\’product_type111\‘ /><b>product_type111</b>’, ‘product_type111′, ‘product_type111′, ‘product_type’, null, ‘1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (‘7′, ‘3′, ‘2′, ‘0′, ‘<input type=\’checkbox\‘ name=\’product_type22\‘ id=\’product_type22\‘ /><b>product_type22</b>’, ‘product_type22′, ‘product_type22′, ‘product_type’, null, ‘1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (‘8′, ‘2′, ‘3′, ‘0′, ‘<input type=\’checkbox\‘ name=\’product_type12\‘ id=\’product_type12\‘ /><b>product_type12</b>’, ‘product_type12′, ‘product_type12′, ‘product_type’, null, ‘1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (‘9′, ‘4′, ‘4′, ‘1′, ‘<input type=\’checkbox\‘ name=\’product_type211\‘ id=\’product_type211\‘ /><b>product_type211</b>’, ‘product_type211′, ‘product_type211′, ‘product_type’, null, ‘1′, null, null, null, null, null);
INSERT INTO `oss_category` VALUES (‘10′, ‘7′, ‘4′, ‘1′, ‘<input type=\’checkbox\‘ name=\’product_type221\‘ id=\’product_type221\‘ /><b>product_type221</b>’, ‘product_type221′, ‘product_type221′, ‘product_type’, null, ‘1′, null, null, null, null, null);
注意:
- 为了支持在树形结构的节点前有checkbox框,需要在树形结构的title(对应s:tree中的nodeTitleProperty)前增加checkbox树形,因此在数据库中category_title字段的格式采用如上的形式,例如:
<input type=’checkbox’ name=’product_type1′ id=’product_type1′ /><b>product_type1</b>
- 对于有序的树形结构,为了标识left和right,可以增加ordering字段,或者采用类似嵌套树的结构,例如
CREATE TABLE nested_category (
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
lft INT NOT NULL,
rgt INT NOT NULL
);
但采用嵌套树的方式,在变动一个节点时候需要对相关的节点进行更新,不是很适合。但效率较高。可以参看:
http://dev.mysql.com/tech-resources/articles/hierarchical-data.html
http://www.sitepoint.com/print/hierarchical-data-database
2、Model层
OssCategory.java
直接用myeclipse生成后,增加了如下内容:
private OssCategory[] childCategories;
private OssCategory parentCategory;
public OssCategory[] getChildCategories() {
return childCategories;
}
public void setChildCategories(OssCategory[] childCategories) {
this.childCategories = childCategories;
}
public OssCategory getParentCategory() {
return parentCategory;
}
public void setParentCategory(OssCategory parentCategory) {
this.parentCategory = parentCategory;
}
3、DAO层
OssCategoryDAO.java
直接用myeclipse生成,没有调优,增加如下内容:
public OssCategory[] getAllCategory() {
ArrayList<OssCategory> resultList=new ArrayList<OssCategory>();
String queryString=“from OssCategory as category where category.parentId=1″;
List<OssCategory> queryList =getHibernateTemplate().find(queryString);
Iterator iterator=queryList.iterator();
while(iterator.hasNext()){
OssCategory ossCategory=(OssCategory)iterator.next();
OssCategory[] childrenArray=getChildCategoriesById(ossCategory.getCategoryId());
logger.debug(“getAllCategory() - OssCategory[] childrenList=” + childrenArray.length);
ossCategory.setChildCategories(childrenArray);
resultList.add(ossCategory);
}
OssCategory[] resultArray=(OssCategory[])resultList.toArray(new OssCategory[resultList.size()]);
return resultArray;
}
public OssCategory[] getChildCategoriesById(int categoryId) {
String queryString=“from OssCategory as category where category.parentId=”+categoryId;
List<OssCategory> queryList =getHibernateTemplate().find(queryString);
logger.debug(“getChildCategoriesById(int) - List<OssCategory> queryList=” + queryList.size());
ArrayList<OssCategory> resultList=new ArrayList<OssCategory>();
Iterator iterator=queryList.iterator();
while(iterator.hasNext()){
OssCategory ossCategory=(OssCategory)iterator.next();
OssCategory[] childrenList=getChildCategoriesById(ossCategory.getCategoryId());
logger.debug(“getChildCategoriesById(int) - OssCategory[] childrenList=” + childrenList+“parentid is “+categoryId); //$NON-NLS-1$
ossCategory.setChildCategories(childrenList);
resultList.add(ossCategory);
}
OssCategory[] resultArray=(OssCategory[])resultList.toArray(new OssCategory[resultList.size()]);
return resultArray;
}
注意:
4、Service层
TreeService.java
有些方法尚未实现
package com.mobilesoft.framework.tree.service;
import java.util.List;
import java.util.Set;
import com.mobilesoft.framework.tree.model.OssCategory;
public interface TreeService {
/**
* @return 获取下级子节点
*/
// public Set getChildren(int rootId);
/**
* @return 递归指定级别的所有子节点
*
*/
// public Set getChildrenByLevel(int rootId,int level);
/**
* @return 获取指定类型的所有子节点
* @param type:节点的类型
* @param levle:节点相对于所属类型的节点的级别 -1表示获取所属类型的根节点的所有级别的子节点
*/
// public Set getChildrenByType( String type,int level);
/**
* @return 获取指定根节点的所有子节点
*/
public OssCategory[] getAllCategory();
}
TreeServiceImpl.java
package com.mobilesoft.framework.tree.service.impl;
import java.util.List;
import java.util.Set;
import com.mobilesoft.framework.tree.dao.hibernate.OssCategoryDAO;
import com.mobilesoft.framework.tree.model.OssCategory;
import com.mobilesoft.framework.tree.service.TreeService;
public class TreeServiceImpl implements TreeService {
OssCategoryDAO ossCategoryDAO;
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 OssCategory[] getAllCategory(){
return ossCategoryDAO.getAllCategory();
}
public OssCategoryDAO getOssCategoryDAO() {
return ossCategoryDAO;
}
public void setOssCategoryDAO(OssCategoryDAO ossCategoryDAO) {
this.ossCategoryDAO = ossCategoryDAO;
}
}
5、Action层
package com.mobilesoft.esales.webapp.action;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import com.mobilesoft.framework.tree.model.FileWrapper;
import com.mobilesoft.framework.tree.model.OssCategory;
import com.mobilesoft.framework.tree.service.TreeService;
public class TreeAction extends BaseAction {
/**
* Logger for this class
*/
private static final Logger logger = Logger.getLogger(TreeAction.class);
private OssCategory root;
private TreeService treeService;
private FileWrapper fileroot;
public String execute() {
root = new OssCategory();
root.setCategoryId(new Integer(1));
root.setParentId(-1);
root.setCategoryTitle(“Root”);
root.setCategoryName(“Root”);
OssCategory[] childCategories = treeService.getAllCategory();
root.setChildCategories(childCategories);
visitTree(childCategories);
getRequest().setAttribute(“root”, root);
// fileroot = new FileWrapper(getSession().getServletContext().getRealPath(”/”));
return SUCCESS;
}
public String treeWithCheckbox() {
Map map = getRequest().getParameterMap();
logger.fatal(“the tree map”);
Set set = map.entrySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
logger.fatal(“the tree key is: “ + entry.getKey() + ” ,and value is: “
+ entry.getValue());
}
return SUCCESS;
}
public TreeService getTreeService() {
return treeService;
}
public void setTreeService(TreeService treeService) {
this.treeService = treeService;
}
public OssCategory getRoot() {
return root;
}
public void setRoot(OssCategory root) {
this.root = root;
}
public void visitTree(OssCategory[] tree) {
for (int i = 0; i < tree.length; i++) {
OssCategory node = (OssCategory) tree[i];
logger.fatal(“visitTree:The treenode parentid is :”
+ node.getParentId() + ” ,treenode id is “
+ node.getCategoryId() + “,and the treenode name is “
+ node.getCategoryName());
visitTree(node.getChildCategories());
}
}
}
6、页面
treetest.jsp
<%@ taglib prefix=”s” uri=”/struts-tags” %>
<%@ page language=”java” errorPage=”/error.jsp” pageEncoding=”GBK” contentType=”text/html;charset=GBK” %>
<html>
<head>
<title>tag list</title>
<%@ include file=”/common/meta.jsp” %>
<style>
.treeCheckBox {
height: 14px;
margin: 0px;
padding: 0px;
border: 1px;
vertical-align: middle;
}
</style>
<s:head theme=”ajax” debug=”true” />
</head>
<body >
<script type=”text/javascript”>
function treeNodeSelected(arg) {
//alert(”id[”+arg.source.widgetId+”], name[”+ arg.source.title+ “] selected”);
}
function treeNodeExpanded(arg) {
//alert(”id[”+arg.source.widgetId+”], name[”+ arg.source.title+ “] expanded”);
}
function treeNodeCollapsed(arg) {
//alert(”id[”+arg.source.widgetId+”], name[”+ arg.source.title+ “] collapsed”);
}
dojo.addOnLoad(function() {
var t = dojo.widget.byId(‘root’);
dojo.event.topic.subscribe(t.eventNames.expand, treeNodeExpanded);
dojo.event.topic.subscribe(t.eventNames.collapse, treeNodeCollapsed);
var s = t.selector;
});
</script>
<s:form name=”form1″ action=”treeWithCheckbox”>
<div style=”float:left; margin-right: 50px;” >
<s:tree
theme=”ajax”
id=”root”
rootNode=”#request.root”
childCollectionProperty=”childCategories”
nodeIdProperty=”categoryId”
nodeTitleProperty=”categoryTitle”
treeSelectedTopic=”treeSelected”>
</s:tree>
</div>
<div style=”float:left; margin-right: 50px;”>
<s:submit/>
</div>
</s:form>
</body>
</html>
注意:
- 在s:tree中不能使用label标签,使用后树形结构出不来。
- childCollectionProperty=”childCategories”对应Category.java中的
private OssCategory[] childCategories;
public OssCategory[] getChildCategories() {
return childCategories;
}
public void setChildCategories(OssCategory[] childCategories) {
this.childCategories = childCategories;
}
-
nodeIdProperty=”categoryId”和nodeTitleProperty=”categoryTitle” 对应OssCategory[] childCategories中每一个Category的categoryId和categoryTitle
-
rootNode=”#request.root”,采用rootNode=”root”方式好像取不到数据,按理说应该可以通过ognl自动调用getRoot方法得到
-
结果提交后,根据对应checkbox的name的on属性来判断是否选中,因此在数据库中对节点的命名需要有一定规则,以便方便选择
尚需要完善的地方:
- 增加对下拉框树形结构实现的支持,以处理项目中大量诸如产品类型、资源类型等类型。
在实现上,为了避免由于浏览器兼容性问题,在形式上是树形结构,实际上就是从字典表(目前树形结构仍然采用oss_category作为字典表)动态取出数据,然后拼凑成字符串形式展现,例如:
<select>
<option value=”1″>1</option>
<option value=”11″> |-11</option>
<option value=”12″> |-12</option>
<option value=”2″>2</option>
<option value=”21″> |-21</option>
<option value=”22″> |-22</option>
</select>
- 进一步优化递归算法,一次性从数据库取出需要的数据,在内存中完成树形结构的构造,避免对数据库的查询,在目前情况下,采用递归方式问题也不大。
- 由于Struts2.0采用dojo来实现其标签,尽管dojo与jquery相比较很重,但由于与Struts2.0较好支持,在架构统一上和使用上还是较为方便的,因此有空需要研究一下dojo库。
7、参考资料
http://www.ajaxtree.com/
http://struts.apache.org/2.x/docs/tree.html
http://ait.web.psi.ch/js/dojo/tests/widget/tree/
http://exjava.spaces.live.com/blog/cns!A43FBDAF9245A0BC!199.entry
http://www.stack.be/~roel/blog/archives/the-dojo-tree-control-for-beginners-part-1/
http://www.stack.be/~roel/blog/archives/the-dojo-tree-control-for-beginners-part-2
http://www.codepencil.com/index.php/struts2-dojo-dynamic-tree/
http://dojotoolkit.org/book/dojo-book-0-9/part-2-dijit/advanced-editing-and-display/tree