在系統中,經(jīng)常會(huì )用到無(wú)限級的樹(shù)形結構分類(lèi),如組織機構管理、商品/地區分類(lèi)等等。在以前的一個(gè)貼子:
http://www.javaeye.com/topic/26987“復雜商品分類(lèi)的表如何建立?”中,討論過(guò)樹(shù)形無(wú)級分類(lèi)的實(shí)現方法。
一般無(wú)外采用兩種方式,
一是類(lèi)似struts-menu(
http://struts-menu.sourceforge.net)的XML文件管理方式,配置起來(lái)比較方便,但很難與系統中其它應用數據集成;
二是使用數據庫存儲,定義父子關(guān)系。
在我們現在開(kāi)發(fā)的一個(gè)產(chǎn)品中,使用hibernate實(shí)現了一套樹(shù)形結構的處理方法,實(shí)現了樹(shù)的基本操作,上溯、下溯、子節點(diǎn)的添加/移除和遞歸查找、對象關(guān)聯(lián)等。簡(jiǎn)介如下:
■適用范圍,具有樹(shù)形特征的所有對象,如樹(shù)形菜單、組織結構、信息分類(lèi)、論壇主貼與回復等。
■演示地址:
http://219.143.69.2:8000/treetest/menumanage.do?todoaction=list演示的是系統菜單的層次實(shí)現。由于菜單本身屬于權限系統的一部分,存儲在數據庫中后可以方便的與部門(mén)、用戶(hù)、崗位、職務(wù)等進(jìn)行關(guān)聯(lián),并進(jìn)行權限控制。
■完整源碼下載(內置了hsql數據庫及測試數據,正式使用時(shí)請將war置于A(yíng)PPSERVER的webapps目錄下,修改解包后的WEB-INF/classes/hibernate.cfg.xml,編輯其中hsqldb的物理路徑。如jdbc:hsqldb:file:c:\tomcat5\webapps\treetest\db\test):
http://219.143.69.2:8000/treetest/treetest.war,運行http://ServerName:ServerPort/treetest/menumanage.do。
■樹(shù)形結構顯示,使用的是xtree。為便于編輯維護,自己寫(xiě)了一個(gè)左鍵彈出菜單(xtree的右鍵事件無(wú)法更改),進(jìn)行節點(diǎn)的添加、修改、刪除、轉移操作。(PS:這套維護界面是完全跨瀏覽器的,有興趣的不妨一試)
■關(guān)聯(lián)關(guān)系:
可以使用objects對象來(lái)配置關(guān)聯(lián)關(guān)系,實(shí)現多對多/一對多等關(guān)系。在BaseTree中,getObjects()方法是abstract的,可以根據需要自己定義。如論壇分類(lèi)與每個(gè)分類(lèi)所對應的貼子相關(guān)聯(lián),商品分類(lèi)與商品編碼相關(guān)聯(lián)等,可以根據需要來(lái)處理hbm文件。若需要多項關(guān)聯(lián),亦可擴展。如菜單與用戶(hù)、部門(mén)、崗位分別進(jìn)行關(guān)聯(lián)
■hibernate2.1.7的一個(gè)bug,在這個(gè)測試源碼的dao中,TreeManager的getRoots方法,
session.createQuery(" from " + cls.getName() + " where enabled=? and parent_id is null order by id");
在hibernate2中必須像寫(xiě)成parent_id is null,才能正確運行,這應該是2.1.7中的一個(gè)bug。而hibernate3中,可以使用parent is null的hsql。
■主要代碼:
繼承關(guān)系如下,假如要實(shí)現國家分類(lèi):
CountryTree extends BaseTree(abstract class)
BaseTree(abstract class) implements Tree(interface)
為節省版面,下面代碼去掉了javadoc
Tree.java
代碼
/**
* 實(shí)現了樹(shù)的基本操作,上溯、下溯、子節點(diǎn)的添加/移除和遞歸查找、對象關(guān)聯(lián)等
*/
package test.testtree.base;
import java.util.Set;
public interface Tree {
public String getCode();
public String getName();
public String getDescription();
public Tree getParent();
public Set getParents();
public boolean isRoot();
public boolean isLeaf();
public boolean isParentOf(Tree tree);
public boolean isChildOf(Tree tree);
public void addChild(Tree tree);
public void rmChild(Tree tree);
public Set getAllChildren();
public Set getChildren();
public Set getAllLeaves();
public void addObject(Object obj);
public void rmObject(Object obj);
public Set getObjects();
public Long getId();
}
BaseTree.java
代碼
package test.testtree.base;
import java.util.*;
public abstract class BaseTree extends BasePojo implements Tree{
protected String code;
protected String name;
protected String description;
protected BaseTree parent;
protected Set children = new HashSet();
protected Set objects = new HashSet();
public void setCode(String code) {
this.code = code;
}
abstract public String getCode();
public void setName(String name) {
this.name = name;
}
abstract public String getName();
public void setDescription(String description) {
this.description = description;
}
abstract public String getDescription();
abstract public Tree getParent();
public boolean isRoot() {
return (getParent()==null);
}
public boolean isLeaf() {
return (this.getChildren().size()==0);
}
public boolean isParentOf(Tree tree) {
if (tree==null || ((BaseTree) tree).equals(this)) {
/*如果對方為空*/
return false;
}else if(this.isLeaf()){
/*如果自己為葉子,則返回FALSE*/
return false;
}else if(tree.isRoot()){
/*如果對方為根,返回FALSE*/
return false;
}else{
BaseTree bt = (BaseTree) (tree.getParent());
if (this.equals(bt)){
/*如果對方的父節點(diǎn)是自己,則返回TRUE*/
return true;
}else{
/*判斷對方的父節點(diǎn)是否是自己的孩子,進(jìn)行遞歸*/
return isParentOf(bt);
}
}
}
public boolean isChildOf(Tree tree) {
return (tree.isParentOf(this));
}
public void addChild(Tree tree) {
children.add(tree);
}
public void rmChild(Tree tree) {
children.remove(tree);
((BaseTree) tree).setParent(null);
}
public Set getAllLeaves() {
Set set_old = this.getAllChildren();
Set set = new HashSet();
set.addAll(set_old);
Iterator itr = set_old.iterator();
while(itr.hasNext()){
BaseTree bt = (BaseTree) itr.next();
if (! bt.isLeaf()){
set.remove(bt);
}
}
return set;
}
public Set getParents(){
Set parents = new HashSet();
Tree p = this.getParent();
if(p!=null){
parents.add(p);
parents.addAll(p.getParents());
}
return parents;
}
public Set getAllChildren() {
Set set = new HashSet();
Stack stack = new Stack();
stack.push(this);
while(!stack.empty()){
BaseTree bt = (BaseTree) stack.pop();
set.add(bt);
Iterator itr = bt.getChildren().iterator();
while(itr.hasNext()){
BaseTree btchild = (BaseTree) itr.next();
stack.push(btchild);
}
}
set.remove(this);
return set;
}
abstract public Set getChildren();
public void addObject(Object obj) {
objects.add(obj);
}
public void rmObject(Object obj) {
objects.remove(obj);
}
abstract public Set getObjects();
public void setParent(Tree parent) {
this.parent = (BaseTree) parent;
}
public void setChildren(Set children) {
this.children = children;
}
public void setObjects(Set objects) {
this.objects = objects;
}
}