Java通用樹結構數據管理


1、前言

​ 樹結構是一種較為常見的數據結構,如功能權限樹、企業的組織結構圖、行政區划結構圖、家族譜、信令消息樹等,都表現為樹型數據結構。

​ 樹結構數據的共性是樹節點之間都有相互關系,對於一個節點對象,可以找到父節點、左鄰節點、右鄰節點、子節點列表。節點本身有其數據類型對象,不同類型的樹,不同之處在於節點數據類型不同。

​ 下面針對樹型數據,用Java語言給出一種通用樹結構數據定義,並提供常規的樹節點操作方法。

2、樹節點類定義

2.1、基本概念

  • 樹節點:即treeNode,樹節點是樹結構的基本元素,整棵樹是由一些列樹節點串接而成。每個樹節點,其父節點、左鄰節點、右鄰節點,或者是唯一的,或者為空,(否則成網狀結構了)。樹節點還包含子節點列表及自身數據對象。
  • 根節點:即rootNode,一棵樹的根節點是唯一的,根節點的父節點為空。常見的樹型結構數據,看起來好像有一組根節點,如導航欄菜單、菜單欄,實際上那是根節點的一級子節點。為了便於數據庫對樹型數據的存儲,根節點的節點ID規定為0。
  • 葉子節點:即leafNode,葉子節點為樹的末梢,葉子節點不包含子節點。
  • 樹:使用樹節點對象來表示一棵樹,由於樹節點包含子節點,子節點又包含子子節點。因此一個樹節點,就是一棵子樹;如果樹節點為根節點,則表示整棵樹。
  • 父節點:樹節點的父節點,當前樹節點在父節點的子節點列表中。
  • 子節點:樹節點的子節點,在當前節點的子節點列表中。
  • 上級節點:或稱祖先節點,從根節點到當前節點的路徑上,不含當前節點的所有節點,都是上級節點。
  • 下級節點:或稱子孫節點,以當前節點為上級節點的所有節點,都是下級節點。
  • 左鄰節點:或稱左兄弟節點,或前一節點,與當前節點擁有相同的父節點,且在父節點的子節點列表中,在當前節點的前一個。
  • 右鄰節點:或稱右兄弟節點,或后一節點,與當前節點擁有相同的父節點,且在父節點的子節點列表中,在當前節點的后一個。
  • 節點數據:每個節點包含一個節點數據對象,不同種類的樹節點,其差異就是節點數據對象類型的不同。

2.2、樹節點類

​ 樹節點類TreeNode,其定義如下:

package com.abc.questInvest.vo;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;


import lombok.Data;

/**
 * @className	: TreeNode
 * @description	: 樹節點
 * @summary	: 節點數據類型,必須實現ITreeNodeData接口類的接口
 *
 */
@Data
public class TreeNode<T extends ITreeNodeData> implements Serializable {
	private static final long serialVersionUID = 1L;

	//節點數據對象
	private T nodeData;
	
	//父節點對象
	private TreeNode<T> parent;
	
	//子節點列表
	private List<TreeNode<T>> children = new ArrayList<TreeNode<T>>();
    
	//是否包含在樹中,1表示包含,0表示不包含,此屬性為附加屬性,在完整樹剪枝時使用
	private Integer isIncluded = 0;    
}

​ 樹節點類TreeNode使用泛型T,來表示節點數據類型,規定T必需實現ITreeNodeData接口類,使用接口類而不是基類,是為了不限定節點數據的字段集,且沒有多重繼承的問題。另外TreeNode也需要實現Serializable接口類,提供節點數據的序列化方法。

​ TreeNode類提供下列屬性字段:

  • nodeData字段,節點數據對象,數據類型為泛型T。使用泛型,來達到通用樹節點的目的。
  • parent字段,父節點對象,類型仍然是TreeNode 。如果父節點為null,表示這是根節點。
  • children字段,子節點列表,其成員仍是TreeNode 類型。
  • isIncluded字段,當前節點是否包含在樹中,有時候,即使某個節點在樹中,通過此屬性字段,仍然可以指示該節點是否需要被剪枝。

​ TreeNode類,提供了父節點對象和子節點列表屬性字段,從而具有向上搜索和向下搜索的能力。

​ TreeNode類,沒有使用左鄰節點、右鄰節點屬性字段,是考慮到兄弟節點的搜索不是很頻繁,不用左鄰節點、右鄰節點屬性字段,可以減少節點關系維護的復雜度。同級節點搜索,可以遍歷父節點的子節點列表來實現。

3、樹節點數據接口類

樹節點數據接口類,為ITreeNodeData,其規定了樹節點數據對象類型,必需實現的接口方法。代碼如下:

package com.abc.questInvest.vo;

/**
 * @className	: ITreeNodeData
 * @description	: 樹節點數據接口類
 *
 */
public interface ITreeNodeData extends Cloneable{
	//=============節點基本屬性訪問接口==============================
	//獲取節點ID
	int getNodeId();

	//獲取節點名稱
	String getNodeName();
	
	//獲取父節點ID
	int getParentId();
	
	//=============Cloneable類接口===================================
	//克隆
	public Object clone();
}

​ ITreeNodeData類,繼承Cloneable,要求樹節點數據對象類型必需實現克隆(clone)接口方法。目的是實現對象復制。

​ ITreeNodeData類定義了下列基本的接口方法:

  • getNodeId方法,獲取節點ID。
  • getNodeName方法,獲取節點名稱。
  • getParentId方法,獲取父節點ID。
  • clone方法,實現Cloneable接口類需要重載的方法。

4、完整的樹節點類

​ 對樹節點類TreeNode,進行完善,提供必要的接口。代碼如下:

package com.abc.questInvest.vo;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;


import lombok.Data;

/**
 * @className	: TreeNode
 * @description	: 樹節點
 * @summary	: 節點數據類型,必須實現ITreeNodeData接口類的接口
 *
 */
@Data
public class TreeNode<T extends ITreeNodeData> implements Serializable {
	private static final long serialVersionUID = 1L;

	//節點數據
	private T nodeData;
	
	//父節點對象
	private TreeNode<T> parent;
	
	//子節點
	private List<TreeNode<T>> children = new ArrayList<TreeNode<T>>();
	
	//是否包含在樹中,1表示包含,0表示不包含,此屬性為附加屬性,在完整樹剪枝時使用
	private Integer isIncluded = 0;
	
	/**
	 * 
	 * @methodName	: addChildNode
	 * @description	: 添加子節點 
	 * @param childNode	: 子節點
	 *
	 */
	public void addChildNode(TreeNode<T> childNode) {
		childNode.setParent(this);
		children.add(childNode);
	}
	
	/**
	 * 
	 * @methodName		: removeChildNode
	 * @description		: 移除子節點,如果子節點在子節點列表中,則移除,否則無影響
	 * @param childNode	: 子節點
	 *
	 */
	public void removeChildNode(TreeNode<T> childNode) {
		children.remove(childNode);
	}
	
	/**
	 * 
	 * @methodName	: clear
	 * @description	: 移除所有子節點
	 *
	 */
	public void clear() {
		children.clear();
	}
    
	/**
	 * 
	 * @methodName		: getPrevSibling
	 * @description		: 取得左鄰節點
	 * @return		: 如果當前節點為第一個節點,則返回null,否則為前一個節點
	 *
	 */
	public TreeNode<T> getPrevSibling(){
		if (parent == null) {
			//如果為根節點,則返回null
			return null;
		}
		
		List<TreeNode<T>> siblingList = parent.getChildren();
		TreeNode<T> node = null;
		for (int i = 0; i < siblingList.size(); i++) {
			TreeNode<T> item = siblingList.get(i);
			if (item == this) {
				//找到當前節點
				if (i > 0) {
					//當前節點不是第一個子節點
					//取得前一個節點
					node = siblingList.get(i-1);
				}
				break;
			}
		}
		return node;		
	}
	
	/**
	 * 
	 * @methodName		: getNextSibling
	 * @description		: 取得右鄰節點
	 * @return		: 如果當前節點為最后一個節點,則返回null,否則為后一個節點
	 *
	 */
	public TreeNode<T> getNextSibling(){
		if (parent == null) {
			//如果為根節點,則返回null
			return null;
		}
		
		List<TreeNode<T>> siblingList = parent.getChildren();
		TreeNode<T> node = null;
		for (int i = 0; i < siblingList.size(); i++) {
			TreeNode<T> item = siblingList.get(i);
			if (item == this) {
				//找到當前節點
				if (i < siblingList.size()-1) {
					//當前節點不是最后一個子節點
					//取得后一個節點
					node = siblingList.get(i+1);
				}
				break;
			}
		}
		return node;		
	}	
	
	/**
	 * 
	 * @methodName		: lookUpSubNode
	 * @description		: 在當前節點及下級節點中查找指定節點ID的節點
	 * @param nodeId	: 節點ID
	 * @return		: 如果找到,返回對應樹節點,否則返回null
	 *
	 */
	public TreeNode<T> lookUpSubNode(int nodeId){
		TreeNode<T> node = null;

		//檢查當前節點
		if (nodeData.getNodeId() == nodeId) {
			node = this;
			return node;
		}
		
		//遍歷子節點
		for(TreeNode<T> item : children) {			
			node = item.lookUpSubNode(nodeId);
			if (node != null) {
				//如果節點非空,表示查找到了
				break;
			}
		}
		return node;
	}
	
	/**
	 * 
	 * @methodName		: lookUpSubNode
	 * @description		: 在當前節點及下級節點中查找指定節點名稱的節點
	 * @param nodeName	: 節點名稱
	 * @return		: 如果找到,返回對應樹節點,否則返回null
	 *
	 */
	public TreeNode<T> lookUpSubNode(String nodeName){
		TreeNode<T> node = null;

		//檢查當前節點
		if (nodeData.getNodeName().equals(nodeName)) {
			node = this;
			return node;
		}
		
		//遍歷子節點
		for(TreeNode<T> item : children) {			
			node = item.lookUpSubNode(nodeName);
			if (node != null) {
				//如果節點非空,表示查找到了
				break;
			}
		}
		return node;		
	}	
    
	/**
	 * 
	 * @methodName		: lookUpSuperNode
	 * @description		: 在當前節點及上級節點中查找指定節點ID的節點
	 * @param nodeId	: 節點ID
	 * @return		: 如果找到,返回對應樹節點,否則返回null
	 *
	 */
	public TreeNode<T> lookUpSuperNode(int nodeId){
		TreeNode<T> node = null;

		//檢查當前節點
		if (nodeData.getNodeId() == nodeId) {
			node = this;
			return node;
		}
		
		//查找父節點
		if (parent != null) {
			node = parent.lookUpSuperNode(nodeId);
		}
		
		return node;
	}
	
	/**
	 * 
	 * @methodName		: lookUpSuperNode
	 * @description		: 在當前節點及上級節點中查找指定節點名稱的節點
	 * @param nodeName	: 節點名稱
	 * @return		: 如果找到,返回對應樹節點,否則返回null
	 *
	 */
	public TreeNode<T> lookUpSuperNode(String nodeName){
		TreeNode<T> node = null;

		//檢查當前節點
		if (nodeData.getNodeName().equals(nodeName)) {
			node = this;
			return node;
		}
		
		//查找父節點
		if (parent != null) {
			node = parent.lookUpSuperNode(nodeName);
		}
		
		return node;
	}
			
	/**
	 * 
	 * @methodName		: clone
	 * @description		: 復制樹節點,包括所有子節點
	 * @return		: 復制后的樹節點
	 *
	 */
	@SuppressWarnings("unchecked")
	public TreeNode<T> clone(){
		//復制當前節點數據信息
		TreeNode<T> treeNode = new TreeNode<T>();
		//節點數據
		treeNode.setNodeData((T)this.nodeData.clone());
		//是否包含
		treeNode.setIsIncluded(this.isIncluded);
		//復制所有子節點
		for(TreeNode<T> item : this.children) {
			//復制子節點
			TreeNode<T> childNode = item.clone();
			//加入子節點列表中
			treeNode.addChildNode(childNode);
		}
		return treeNode;
	}
	
	/**
	 * 
	 * @methodName		: setChildrenIsIncluded
	 * @description		: 設置所有子節點的是否包含屬性 
	 * @param isIncluded	: 節點是否包含
	 *
	 */
	public void setChildrenIsIncluded(Integer isIncluded) {
		//遍歷所有子節點
		for(TreeNode<T> item : this.children) {
			item.setIsIncluded(isIncluded);
			//子節點的子節點
			item.setChildrenIsIncluded(isIncluded);
		}
	}
	
	/**
	 * 
	 * @methodName		: combineTreeNode
	 * @description		: 將結構完全相同的節點合並到本節點中,合並后的節點的isIncluded屬性位|操作
	 * @param combineNode   : 並入的節點
	 *
	 */
	public void combineTreeNode(TreeNode<T> combineNode) {
		//當前節點數據的isIncluded屬性,使用位|操作
		this.setIsIncluded(this.getIsIncluded() | combineNode.getIsIncluded());
		//合並子節點
		for (int i = 0; i < children.size(); i++) {
			TreeNode<T> item = children.get(i);
			TreeNode<T> combineItem = combineNode.getChildren().get(i);
			//合並子節點
			item.combineTreeNode(combineItem);
		}
	}
	
	/**
	 * 
	 * @methodName	: arrange
	 * @description	: 對樹進行剪枝處理,即所有isIncluded為0的節點,都被移除
	 *
	 */
	public void arrange() {
		//遍歷子節點列表,如果子節點的isIncluded為0,則剪枝
		//倒序遍歷
		for (int i = children.size() -1; i >=0; i--) {
			TreeNode<T> item = children.get(i);
			if (item.getIsIncluded() == 0) {
				//不包含,需要移除
				children.remove(i);
			}else {
				//包含,當前節點不需要移除,處理其子節點列表
				item.arrange();
			}			
		}
	}
	
	/**
	 * 
	 * @methodName		: getNodeList
	 * @description		: 獲取包括自身及所有子節點的列表
	 * @param nodeList	: 樹節點列表,入口參數為null
	 * @return		: 樹節點列表
	 *
	 */
	public List<TreeNode<T>> getNodeList(List<TreeNode<T>> nodeList){
		
		if (nodeList == null) {
			//如果入口節點,則參數為null,需要創建
			nodeList = new ArrayList<TreeNode<T>>();
		}
		
		//加入自身節點
		nodeList.add(this);
		
		//加入所有子節點
		for(int i = 0; i < children.size(); i++) {
			TreeNode<T> childNode = children.get(i);
			childNode.getNodeList(nodeList);
		}
		
		return nodeList;
	}
	
	/**
	 * 
	 * @methodName		: loadData
	 * @description		: 將T類型對象的列表加載到樹中,調用之前應確保節點的數據對象已創建,
	 * 						且節點ID設置為0
	 * @param inputList	: T類型對象的列表
	 * @return		: 錯誤的T類型對象的列表
	 *
	 */
	public List<T> loadData(List<T> inputList){
		//錯誤的數據對象列表
		List<T> errorList = new ArrayList<T>();
		
		//建立節點ID與節點對象的映射表,表示節點加載過程當前已加載的節點集合
		Map<Integer,TreeNode<T>> nodeMap = new HashMap<Integer,TreeNode<T>>();

		//==================================================================
		//要考慮數據次序不一定保證父節點已先加載的情況
		
		//清除數據
		clear();
		//先加入根節點
		nodeMap.put(this.nodeData.getNodeId(), this);
		
		//父節點
		TreeNode<T> parentNode = null;
		
		//遍歷inputList,加載樹				
		for(T item : inputList) {
			Integer parentId = item.getParentId();
								
			if (nodeMap.containsKey(parentId)) {
				//如果父節點已加載,取得父節點對象
				parentNode = nodeMap.get(parentId);
				//加載樹節點
				addTreeNode(parentNode,item,nodeMap);							
			}else {
				//如果父節點未加載,則暫時作為游離的獨立節點或子樹
				//加載樹節點
				addTreeNode(null,item,nodeMap);							
			}											
		}
		
		//處理游離的節點
		for(TreeNode<T> node : nodeMap.values()) {
			if (node.getParent() == null && node.getNodeData().getNodeId() != 0) {
				//父節點為空,且非根節點
				//取得父節點ID
				Integer parentId = node.getNodeData().getParentId();
				if (nodeMap.containsKey(parentId)) {
					//如果父節點存在,,取得父節點對象
					parentNode = nodeMap.get(parentId);
					//加入父節點中
					parentNode.addChildNode(node);
				}else {
					//parentId對應的節點不存在,說明數據配置錯誤
					errorList.add(node.getNodeData());
				}
			}
		}
		return errorList;
	}
	
	/**
	 * 
	 * @methodName		: addTreeNode
	 * @description		: 加入樹節點
	 * @param parentNode	: 父節點
	 * @param dataInfo	: 節點信息對象
	 * @param nodeMap	: 節點ID與節點對象的映射表
	 *
	 */
	private void addTreeNode(TreeNode<T> parentNode, T dataInfo,
			Map<Integer,TreeNode<T>> nodeMap) {
		//生成樹節點
		TreeNode<T> treeNode = new TreeNode<T>();
		//設置節點數據
		treeNode.setNodeData((T)dataInfo);
		if(parentNode != null) {
			//父節點非空,加入父節點中
			parentNode.addChildNode(treeNode);
		}
		
		//加入nodeMap中
		nodeMap.put(dataInfo.getNodeId(), treeNode);		
	}

	/**
	 * 
	 * @methodName		: toString
	 * @description		: 重載toString方法
	 * @return		: 返回序列化輸出的字符串
	 *
	 */
	@Override
	public String toString() {
		String sRet = "";
		
		//根節點的數據部分不必輸出
		if (parent != null) {
			//非根節點
			//輸出節點開始符號
			sRet = "{";			
			//輸出isIncluded值,剪枝后的樹,無需輸出此字段
			//sRet += "\"isIncluded\":" + isIncluded + ",";
			//輸出當前節點數據
			sRet += "\"nodeData\":" + nodeData.toString();
			//與前一個節點分隔
			sRet += ",";			
			sRet += "\"children\":";
		}
		
		//輸出子節點
		//子節點列表
		sRet += "[";
		String sChild = "";
		//遍歷子節點
		for(TreeNode<T> item : children) {
			//輸出子節點數據
			if (sChild.equals("")) {
				sChild = item.toString();
			}else {
				sChild += "," + item.toString();
			}
		}
		sRet += sChild;
		//結束列表
		sRet += "]";
		if (parent != null) {
			//輸出節點結束符號
			sRet += "}";
		}
		
		return sRet;
	}	
}

TreeNode類提供下列接口方法:

  • addChildNode方法,添加一個子節點。
  • removeChildNode方法,刪除一個子節點。
  • clear方法,移除所有子節點。
  • getPrevSibling方法,取得左鄰節點。
  • getNextSibling方法,取得右鄰節點。
  • lookUpSubNode(int)方法,在當前節點及下級節點中查找指定節點ID的節點。
  • lookUpSubNode(String)方法,在當前節點及下級節點中查找指定節點名稱的節點。
  • lookUpSuperNode(int)方法,在當前節點及上級節點中查找指定節點ID的節點。
  • lookUpSuperNode(String)方法,在當前節點及上級節點中查找指定節點名稱的節點。
  • clone方法,復制當前樹節點表示的樹或子樹。
  • setChildrenIsIncluded方法,設置全部下級節點的isIncluded屬性值。
  • combineTreeNode方法,將結構完全相同的節點合並到本節點中,合並后的節點的isIncluded屬性作位或運算。兩棵樹合並,用完全相同結構的樹合並要比不同結構的樹合並要方便很多,如多種角色組合的權限樹,先用全樹合並,然后再剪枝,會方便很多。
  • arrange方法,對樹進行剪枝處理,即所有isIncluded為0的節點,都被移除。
  • getNodeList方法,將所有節點對象(包含自身節點及所有下級節點),輸出到列表中,便於外部進行遍歷訪問。由於樹的遍歷,需要遞歸,外部不好訪問。
  • loadData方法,將T類型對象的列表數據加載到樹中。此方法要求外部先設置根節點的節點ID為0,結果實現樹的構建,並輸出未正確配置的數據對象列表。
  • toString方法,實現Serializable接口類需要重載的方法,提供樹結構數據的序列化輸出。

5、樹結構的節點數據類示例

​ 樹結構的節點數據,以權限管理的功能權限樹為例,實體類為FunctionInfo。代碼如下:

package com.abc.questInvest.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Id;

import com.abc.questInvest.vo.ITreeNodeData;

import lombok.Data;

/**
 * @className	: FunctionInfo
 * @description	: 功能節點信息
 *
 */
@Data
public class FunctionInfo implements Serializable,ITreeNodeData {
	private static final long serialVersionUID = 1L;

	//功能ID
	@Id
	@Column(name = "func_id")
	private Integer funcId;
			
	//功能名稱
	@Column(name = "func_name")
	private String funcName;
	
	//父節點ID
	@Column(name = "parent_id")
	private Integer parentId;
	
	//功能所在層級
	@Column(name = "level")
	private Byte level;	
	
	//顯示順序
	@Column(name = "order_no")
	private Integer orderNo;	

	//訪問接口url
	@Column(name = "url")
	private String url;		
	
	//dom對象的id
	@Column(name = "dom_key")
	private String domKey;		
		
	// ================ 接口重載 ======================
	
	//獲取節點ID
	@Override
	public int getNodeId() {
		return funcId;
	}

	//獲取節點名稱
	@Override
	public String getNodeName() {
		return funcName;
	}
	
	//獲取父節點ID
	@Override
	public int getParentId() {
		return parentId;
	}
	
	//對象克隆
	@Override
	public Object clone(){
		FunctionInfo obj = null;
        try{
        	obj = (FunctionInfo)super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return obj;
    }
	
	@Override
	public String toString() {
		return "{"
				+ "\"funcId\":" + funcId + ","
				+ "\"funcName\":\"" + funcName + "\","
				+ "\"parentId\":" + parentId + ","
				+ "\"level\":" + level + ","
				+ "\"orderNo\":" + orderNo + ","
				+ "\"url\":\"" + url + "\","
				+ "\"domKey\":\"" + domKey + "\""
				+ "}";
	}

}

FunctionInfo類對應數據庫的功能樹表function_tree,表結構如下:

DROP TABLE IF EXISTS `function_tree`;
CREATE TABLE `function_tree`
(
  `func_id`       INT(11)      NOT NULL DEFAULT 0 COMMENT '功能ID',
  `func_name`     VARCHAR(100) NOT NULL DEFAULT '' COMMENT '功能名稱',
  `parent_id`     INT(11)      NOT NULL DEFAULT 0 COMMENT '父功能ID',
  `level`         TINYINT(4)   NOT NULL DEFAULT 0 COMMENT '功能所在層級',
  `order_no`	  INT(11)      NOT NULL DEFAULT 0 COMMENT '顯示順序',
  `url`			  VARCHAR(80) NOT NULL DEFAULT '' COMMENT '訪問接口url',
  `dom_key`       VARCHAR(80) NOT NULL DEFAULT '' COMMENT 'dom對象的id',

  `remark`        VARCHAR(200) NOT NULL DEFAULT '' COMMENT '備注',

  -- 記錄操作信息
  `operator_name` VARCHAR(80)  NOT NULL DEFAULT '' COMMENT '操作人賬號',
  `delete_flag`   TINYINT(4)   NOT NULL DEFAULT 0 COMMENT '記錄刪除標記,1-已刪除',
  `create_time`   DATETIME(3)  NOT NULL DEFAULT NOW(3) COMMENT '創建時間',
  `update_time`   DATETIME(3)           DEFAULT NULL ON UPDATE NOW(3) COMMENT '更新時間',
  PRIMARY KEY (`func_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8 COMMENT ='功能表';

6、功能樹數據服務

6.1、Dao類

​ Dao類為FunctionTreeDao。代碼如下:

package com.abc.questInvest.dao;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import com.abc.questInvest.entity.FunctionInfo;

/**
 * @className	: FunctionTreeDao
 * @description	: function_tree表數據訪問類
 *
 */
@Mapper
public interface FunctionTreeDao {

	//查詢所有功能樹表記錄,按parent_id,order_no排序
	@Select("SELECT func_id,func_name,parent_id,level,order_no,url,dom_key"
			+ " FROM function_tree ORDER BY parent_id,order_no")
	List<FunctionInfo> selectAll();
}

​ 注意,查詢數據需要按parent_id,order_no排序,有助於提高加載速度。

6.2、Service類

​ Service類為FunctionTreeService。代碼如下:

package com.abc.questInvest.service;

import com.abc.questInvest.entity.FunctionInfo;
import com.abc.questInvest.vo.TreeNode;

/**
 * @className	: FunctionTreeService
 * @description	: 功能樹服務
 *
 */
public interface FunctionTreeService {

	/**
	 * 
	 * @methodName		: loadData
	 * @description		: 加載數據庫中數據 
	 * @return		: 成功返回true,否則返回false。
	 *
	 */		
	public boolean loadData();
		
	/**
	 * 
	 * @methodName		: getFunctionTree
	 * @description		: 獲取整個功能樹
	 * @return		: 完整的功能樹
	 *
	 */
	public TreeNode<FunctionInfo> getFunctionTree();
}

6.3、ServiceImpl類

​ Service實現類為FunctionTreeServiceImpl。代碼如下:

package com.abc.questInvest.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.abc.questInvest.dao.FunctionTreeDao;
import com.abc.questInvest.entity.FunctionInfo;
import com.abc.questInvest.service.FunctionTreeService;
import com.abc.questInvest.vo.TreeNode;

import lombok.extern.slf4j.Slf4j;

/**
 * @className	: FunctionTreeServiceImpl
 * @description	: FunctionTreeService實現類
 *
 */
@Slf4j
@Service
public class FunctionTreeServiceImpl implements FunctionTreeService {
	
	//function_tree表數據訪問對象
	@Autowired
	private FunctionTreeDao functionTreeDao;
	
	//功能樹對象
	private TreeNode<FunctionInfo> functionTree = new TreeNode<FunctionInfo>();
	
	/**	
	 * 
	 * @methodName		: loadData
	 * @description		: 加載數據庫中數據 
	 * @return		: 成功返回true,否則返回false。
	 *
	 */
	@Override
	public boolean loadData() {
		
		try {
			//查詢function_tree表,獲取全部數據
			List<FunctionInfo> functionInfoList = functionTreeDao.selectAll();
			
			//加鎖保護,防止臟讀
			synchronized(functionTree) {
				//設置根節點
				setRootNode(functionTree);				
				List<FunctionInfo> errorList = functionTree.loadData(functionInfoList);
				if (errorList.size() > 0) {
					//有錯誤信息
					//寫日志
					for(FunctionInfo item : errorList) {
						log.error("FunctionTree error with item : " + item.toString());
					}
					//此時,functionTree是剔除了異常數據的功能樹
					//返回true或false,視業務需求而定
					return false;
				}
			}				
			
		}catch(Exception e) {
			log.error(e.getMessage());
			e.printStackTrace();
			return false;
		}
		
		return true;
	 }
		
	/**
	 * 
	 * @methodName		: getFunctionTree
	 * @description		: 獲取整個功能樹
	 * @return		: 完整的功能樹
	 *
	 */
	@Override
	public TreeNode<FunctionInfo> getFunctionTree(){
		return functionTree;
	}
	
	/**
	 * 
	 * @methodName		: setRootNode
	 * @description		: 設置根節點
	 * @param node		: 輸入的功能樹根節點
	 *
	 */
	private void setRootNode(TreeNode<FunctionInfo> node) {
		node.setParent(null);
		//創建空節點數據
		node.setNodeData(new FunctionInfo());
		//約定根節點的節點ID為0
		node.getNodeData().setFuncId(0);
		node.getNodeData().setFuncName("root");
		//根節點總是包含的
		node.setIsIncluded(1);		
	}	
}

​### 6.4、單元測試

​ 對FunctionTreeService使用單元測試,觀察效果。代碼如下:

/**
 * @className	: QuestInvestApplicationTest
 * @description	: 啟動測試類
 *
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class QuestInvestApplicationTest {
    @Autowired
    ServletContext servletContext;
    
    @Autowired
    FunctionTreeService functionTreeService;
    
	@Test
	public void functionTreeServiceTest() {
		boolean bRet = false;
		bRet = functionTreeService.loadData();
		if (bRet) {		
			TreeNode<FunctionInfo> functionTree = functionTreeService.getFunctionTree();
			System.out.println(functionTree);
		}
	}
}

執行測試代碼,可以看到輸出的功能樹數據,將之用網上的JSON查看器查看,可以看到下圖的樹型結構:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM