zoukankan      html  css  js  c++  java
  • 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查看器查看,可以看到下图的树型结构:

    作者:阿拉伯1999
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
    养成良好习惯,好文章随手顶一下。
  • 相关阅读:
    Binary Tree Inorder Traversal
    Populating Next Right Pointers in Each Node
    Minimum Depth of Binary Tree
    Majority Element
    Excel Sheet Column Number
    Reverse Bits
    Happy Number
    House Robber
    Remove Linked List Elements
    Contains Duplicate
  • 原文地址:https://www.cnblogs.com/alabo1999/p/14928380.html
Copyright © 2011-2022 走看看