• 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
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
    养成良好习惯,好文章随手顶一下。
  • 相关阅读:
    python之九九乘法表
    python之生成随机密码
    selenium以及浏览器驱动下载安装
    Monkey命令
    Android SDK Manager仅有一个版本的问题
    截图工具无法使用解决方法
    jmeter服务器监控插件指标简单说明
    mysql字符集小结
    mysql创建新的用户及flush privileges解析
    MySql添加远程超级管理员用户
  • 原文地址:https://www.cnblogs.com/alabo1999/p/14928380.html
Copyright © 2020-2023  润新知