在项目中有很多地方使用了树形结构,主要有几个地方:素材和软件等软件资源表,人员权限的权限资源表、人员权限的角色,套餐及产品包,现以类别表为例,说明树形结构的数据库存储、利用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
-
mysql对树形结构没有原生的支持,Oracle和DB2可以支持,Oracle通过CONNECT BY,DB2通过With方式。
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; }
注意:
-
getAllCategory和getChildCategoriesById采用了递归调用方式,应该可以一次性取出指定节点的所有子节点到结果集合List,然后对List在内存中进行遍历,但比较麻烦,先采用此种方式。
-
struts2的tree应该支持List,而不一定要采用数组方式,只不过没有测试
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://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
转载请注明:出家如初,成佛有余 » Struts2 Tree标签的使用