目录
- 前言
- 1. 基本框架
- 2. 实战应用
前言
对应的每个子孙属于该父亲,这其实是数据结构的基础知识,那怎么划分怎么归属呢
对应的基本知识推荐如下:
- 【数据结构】树和二叉树详细分析(全)
- 【数据结构】B树和B+树的笔记详细诠释
1. 基本框架
最基本的树形结构节点,一个自身ID,一个父亲ID,一个孩子ID:
import java.io.Serializable;
import java.util.List;public interface INode<T> extends Serializable {Long getId();Long getParentId();List<T> getChildren();default Boolean getHasChildren() {return false;}
}
其中ForestNodeManager
树形管理类主要表示如下:
nodeMap
存储节点,并提供了一些方法来操作节点和管理树状结构getRoot
方法用于获取合并后的树的根节点列表addParentId
方法用于标记尚未创建的父节点
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;public class ForestNodeManager<T extends INode<T>> {/*** 使用 ImmutableMap 存储节点,其中键是节点的ID,值是节点本身。* 这个映射是不可变的,通过调用 Maps.uniqueIndex(nodes, INode::getId) 来创建,其中 INode::getId 是获取节点ID的方法。* * parentIdMap: 使用 HashMap 存储尚未创建的父节点的ID。键是父节点的ID,值是一个占位对象(在这里是空字符串)。*/private final ImmutableMap<Long, T> nodeMap;private final Map<Long, Object> parentIdMap = Maps.newHashMap();/*** 接受一个 List<T> 类型的参数 nodes,并使用 Maps.uniqueIndex 方法将其转换为 ImmutableMap 存储在 nodeMap 中*/public ForestNodeManager(List<T> nodes) {this.nodeMap = Maps.uniqueIndex(nodes, INode::getId);}/*** 接受一个 Long 类型的节点ID作为参数,然后尝试从 nodeMap 中获取对应ID的节点。* 如果存在该节点,则返回节点;否则返回 null。*/public INode<T> getTreeNodeAt(Long id) {return this.nodeMap.containsKey(id) ? (INode)this.nodeMap.get(id) : null;}/*** 接受一个 Long 类型的父节点ID作为参数,将其添加到 parentIdMap 中。* 这个方法用于标记尚未创建的父节点。*/public void addParentId(Long parentId) {this.parentIdMap.put(parentId, "");}/*** 创建一个空的 ArrayList 用于存储树的根节点。* 使用 forEach 遍历 nodeMap 中的每个节点。* 对于每个节点,如果其父节点ID为0(表示是根节点)或者父节点ID在 parentIdMap 中存在(即尚未创建的父节点),则将该节点添加到根节点列表中。* 最后返回根节点列表。*/public List<T> getRoot() {List<T> roots = new ArrayList();this.nodeMap.forEach((key, node) -> {if (node.getParentId() == 0L || this.parentIdMap.containsKey(node.getId())) {roots.add(node);}});return roots;}
}
对于ForestNodeMerger
合并类:
包含树状结构节点的列表,根据节点之间的父子关系进行合并,最终返回合并后的树的根节点列表。
import java.util.List;public class ForestNodeMerger {public ForestNodeMerger() {}/*** 对于每个节点,通过 getParentId() 方法获取其父节点的ID。* 如果父节点ID不等于0(即有父节点),则尝试通过 forestNodeManager.getTreeNodeAt() 方法获取父节点。* 如果成功获取到父节点,则将当前节点添加到父节点的子节点列表中。否则,说明父节点尚未被创建,需要通过 forestNodeManager.addParentId() 方法添加到待创建父节点的列表中。* * 最后,通过 forestNodeManager.getRoot() 方法获取合并后的树的根节点列表,并返回这个列表。*/public static <T extends INode<T>> List<T> merge(List<T> items) {ForestNodeManager<T> forestNodeManager = new ForestNodeManager(items);items.forEach((forestNode) -> {if (forestNode.getParentId() != 0L) {INode<T> node = forestNodeManager.getTreeNodeAt(forestNode.getParentId());if (node != null) {node.getChildren().add(forestNode);} else {forestNodeManager.addParentId(forestNode.getId());}}});return forestNodeManager.getRoot();}
}
2. 实战应用
大多数本身Mapper 或者 Service基层都会帮我们实现增删改查,主要是XML文件对数据库的逻辑
本身就是MybatisPlus框架,推荐阅读:
- Springboot整合MybatisPlus的基本CRUD(全)
- MyBatis-plus从入门到精通(全)
与日常开发差不多,主要是上述中的方法如何套用!
假设要做一个菜单专栏,必须由这种树形结构来管理,好方便那个子类归属那个父类!
对应的类别如下:
@Data
@TableName("menu")
@ApiModel(value = "Menu对象", description = "Menu对象")
public class Menu implements Serializable {private static final long serialVersionUID = 1L;/*** 主键*/@JsonSerialize(using = ToStringSerializer.class)@ApiModelProperty(value = "主键")@TableId(value = "id", type = IdType.ASSIGN_ID)private Long id;/*** 菜单父主键*/@JsonSerialize(using = ToStringSerializer.class)@ApiModelProperty(value = "菜单父主键")private Long parentId;/*** 菜单编号*/@ApiModelProperty(value = "菜单编号")private String code;/*** 菜单名称*/@ApiModelProperty(value = "菜单名称")private String name;/*** 菜单别名*/@ApiModelProperty(value = "菜单别名")private String alias;/*** 请求地址*/@ApiModelProperty(value = "请求地址")private String path;/*** 菜单资源*/@ApiModelProperty(value = "菜单资源")private String source;/*** 排序*/@ApiModelProperty(value = "排序")private Integer sort;/*** 菜单类型*/@ApiModelProperty(value = "菜单类型")private Integer category;/*** 操作按钮类型*/@ApiModelProperty(value = "操作按钮类型")private Integer action;/*** 是否打开新页面*/@ApiModelProperty(value = "是否打开新页面")private Integer isOpen;/*** 备注*/@ApiModelProperty(value = "备注")private String remark;/*** 是否已删除*/@TableLogic@ApiModelProperty(value = "是否已删除")private Integer isDeleted;@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (obj == null) {return false;}Menu other = (Menu) obj;if (Func.equals(this.getId(), other.getId())) {return true;}return false;}}
对应与前端交互的VO类如下:public class MenuVO extends Menu implements INode<MenuVO>
,大部分这里都是这种写法!
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "MenuVO对象", description = "MenuVO对象")
public class MenuVO extends Menu implements INode<MenuVO> {private static final long serialVersionUID = 1L;/*** 主键ID*/@JsonSerialize(using = ToStringSerializer.class)private Long id;/*** 父节点ID*/@JsonSerialize(using = ToStringSerializer.class)private Long parentId;/*** 子孙节点*/@JsonInclude(JsonInclude.Include.NON_EMPTY)private List<MenuVO> children;/*** 是否有子孙节点*/@JsonInclude(JsonInclude.Include.NON_EMPTY)private Boolean hasChildren;@Overridepublic List<MenuVO> getChildren() {if (this.children == null) {this.children = new ArrayList<>();}return this.children;}/*** 上级菜单*/private String parentName;/*** 菜单类型*/private String categoryName;/*** 按钮功能*/private String actionName;/*** 是否新窗口打开*/private String isOpenName;
}
对应的懒加载实现类主要如下:
@Overridepublic List<MenuVO> lazyList(Long parentId, Map<String, Object> param) {if (Func.isEmpty(Func.toStr(param.get("parentId")))) {parentId = null;}return baseMapper.lazyList(parentId, param);}
对应获取树形结构的实现类如下:
@Overridepublic List<MenuVO> tree() {return ForestNodeMerger.merge(baseMapper.tree());}
其中涉及Mapper的方法主要在xml文件中实现:
<select id="tree" resultMap="treeNodeResultMap">select id, parent_id, name as title, id as "value", id as "key" from menu where is_deleted = 0 and category = 1
</select>