可以使用很多开源的工具进行处理,我推荐你一个进行使用 以及使用方案如下
在项目的pom.xml的dependencies中加入以下内容:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.8</version>
</dependency>
引入好依赖之后 可以直接在一个新建的方法中使用工具生成适合自己的开发树形逻辑
考虑到菜单等需求的普遍性,有用户提交了一个扩展性极好的树状结构实现。这种树状结构可以根据配置文件灵活的定义节点之间的关系,也能很好的兼容关系数据库中数据。实现
关系型数据库数据 <-> Tree <-> JSON
Copy to clipboardErrorCopied
树状结构中最大的问题就是关系问题,在数据库中,每条数据通过某个字段关联自己的父节点,每个业务中这个字段的名字都不同,如何解决这个问题呢?
PR的提供者提供了一种解决思路:自定义字段名,节点不再是一个bean,而是一个map,实现灵活的字段名定义。
我们假设要构建一个菜单,可以实现系统管理和店铺管理,菜单的样子如下:
系统管理
|- 用户管理
|- 添加用户
店铺管理
|- 商品管理
|- 添加商品
Copy to clipboardErrorCopied
那这种结构如何保存在数据库中呢?一般是这样的:
idparentIdnameweight10系统管理5111用户管理101111用户添加1120店铺管理5212商品管理102212添加添加11
我们看到,每条数据根据parentId
相互关联并表示层级关系,parentId
在这里也叫外键。
// 构建node列表
List<TreeNode<String>> nodeList = CollUtil.newArrayList();
nodeList.add(new TreeNode<>("1", "0", "系统管理", 5));
nodeList.add(new TreeNode<>("11", "1", "用户管理", 222222));
nodeList.add(new TreeNode<>("111", "11", "用户添加", 0));
nodeList.add(new TreeNode<>("2", "0", "店铺管理", 1));
nodeList.add(new TreeNode<>("21", "2", "商品管理", 44));
nodeList.add(new TreeNode<>("221", "2", "商品管理2", 2));
Copy to clipboardErrorCopied
TreeNode表示一个抽象的节点,也表示数据库中一行数据。 如果有其它数据,可以调用setExtra
添加扩展字段。
// 0表示最顶层的id是0
List<Tree<String>> treeList = TreeUtil.build(nodeList, "0");
Copy to clipboardErrorCopied
因为两个Tree是平级的,再没有上层节点,因此为List。
//配置
TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
// 自定义属性名 都要默认值的
treeNodeConfig.setWeightKey("order");
treeNodeConfig.setIdKey("rid");
// 最大递归深度
treeNodeConfig.setDeep(3);
//转换器
List<Tree<String>> treeNodes = TreeUtil.build(nodeList, "0", treeNodeConfig,
(treeNode, tree) -> {
tree.setId(treeNode.getId());
tree.setParentId(treeNode.getParentId());
tree.setWeight(treeNode.getWeight());
tree.setName(treeNode.getName());
// 扩展属性 ...
tree.putExtra("extraField", 666);
tree.putExtra("other", new Object());
});
Copy to clipboardErrorCopied
通过TreeNodeConfig我们可以自定义节点的名称、关系节点id名称,这样就可以和不同的数据库做对应。
试一下这个工具类
public class TreeUtils {
/**
* 根据pid,构建树节点
*/
public static <T extends TreeNode> List<T> build(List<T> treeNodes, Long pid) {
//pid不能为空
AssertUtils.isNull(pid, "pid");
List<T> treeList = new ArrayList<>();
for(T treeNode : treeNodes) {
if (pid.equals(treeNode.getPid())) {
treeList.add(findChildren(treeNodes, treeNode));
}
}
return treeList;
}
/**
* 查找子节点
*/
private static <T extends TreeNode> T findChildren(List<T> treeNodes, T rootNode) {
for(T treeNode : treeNodes) {
if(rootNode.getId().equals(treeNode.getPid())) {
rootNode.getChildren().add(findChildren(treeNodes, treeNode));
}
}
return rootNode;
}
/**
* 构建树节点
*/
public static <T extends TreeNode> List<T> build(List<T> treeNodes) {
List<T> result = new ArrayList<>();
//list转map
Map<Long, T> nodeMap = new LinkedHashMap<>(treeNodes.size());
for(T treeNode : treeNodes){
nodeMap.put(treeNode.getId(), treeNode);
}
for(T node : nodeMap.values()) {
T parent = nodeMap.get(node.getPid());
if(parent != null && !(node.getId().equals(parent.getId()))){
parent.getChildren().add(node);
continue;
}
result.add(node);
}
return result;
}
}
public class TreeNode<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
private Long id;
/**
* 上级ID
*/
private Long pid;
/**
* 子节点列表
*/
private List<T> children = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getPid() {
return pid;
}
public void setPid(Long pid) {
this.pid = pid;
}
public List<T> getChildren() {
return children;
}
public void setChildren(List<T> children) {
this.children = children;
}
}
用法:
TreeUtils.build(dtoList, Constant.MENU_ROOT);
/** * 菜单根节点标识 */ Long MENU_ROOT = 0L;
public class Demo{
private static List<String> fieldName;
static {
Field[] fields = Data.class.getDeclaredFields();
List<String> excludeName = Arrays.asList("id", "pid", "text", "serialVersionUID");
fieldName = Arrays.stream(fields).filter(f -> {
String name = f.getName();
return !excludeName.contains(name);
}).map(f -> f.getName()).collect(Collectors.toList());
}
public List<Tree<String>> find(String comId){
//伪代码,从数据库查询你的数据
List<Data> datas= mapper.find();
//ed37d86256a14470a.....传入你的树最顶层的父id
List<Tree<String>> build = TreeUtil.build(datas, "ed37d86256a14470a.....", (data, tree) -> {
tree.setId(data.getId());
tree.setParentId(data.getIds());
tree.setWeight(0);
tree.setName(data.getName());
//扩展字段
fieldName.stream().forEach(f -> {
try {
tree.putExtra(f, MetaObjectUtil.getObjectValue(com, f));
} catch (NoSuchFieldException e) {
e.printStackTrace();
log.error("树转换失败!");
}
});
});
return build;
}
}
//按照你的数据创建的bean
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToStringclass Data{
private String id;
private String ids;
private String name;
private String lft;
private String rgt;
private String lvl;
}
用的是Hutool工具包
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.5.8</version> </dependency>
用这个方法即可,链接:https://blog.csdn.net/mangomango123/article/details/113350804
学到了