前言
所谓从数据库中查询的数据进行树形打印,主要因为一个表中设计了当前记录id和该记录的父级id的字段,使得该表的数据形成了级联的关系。表模拟:
字段 | 类型 | 注释 |
id | int | 主键id信息 |
pid | int | 父级id信息,一级设置为0 |
name | varchar | 名称属性 |
通常,我们都是将所有的记录查询出来,在通过业务处理,将数据封装成树形结构。
具体思路我们可以通过递归思想进行数据的处理和封装
具体实现
准备工作:创建一个SpringBoot工程,设计一个Controller组件模拟一个接口。
设计数据实体:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TreeNode {
private Integer id;
private String name;
private Integer pid;
// childrenNode属性用于保存当前记录的子节点信息
// 注意:数据中对应的实体没有该字段,我们可以从数据库中查询所有记录后,再使用BeanUtils或MapStruct相关工具进行转换
private List<TreeNode> childrenNode;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TreeNode treeNode = (TreeNode) o;
return Objects.equals(id, treeNode.id) && Objects.equals(name, treeNode.name) && Objects.equals(pid, treeNode.pid) && Objects.equals(childrenNode, treeNode.childrenNode);
}
@Override
public int hashCode() {
return Objects.hash(id, name, pid, childrenNode);
}
}
进行数据处理和封装:
@RestController
@RequestMapping("/treeNode")
@Slf4j
public class HelloController {
/**
* 单个父节点模式
*
* @return
*/
@GetMapping("/oneFather")
public TreeNode listOne() {
List<TreeNode> nodes = new ArrayList<>();
// 封装数据:模拟数据数据
// 注意:从数据库中查询到的数据没有childrenNode属性,那我们可以使用BeanUtils和mapstruct进行对象转换
Collections.addAll(nodes,
TreeNode.builder().id(1).pid(0).name("江西省").build(),
TreeNode.builder().id(2).pid(1).name("吉安市").build(),
TreeNode.builder().id(3).pid(2).name("遂川县").build(),
TreeNode.builder().id(5).pid(1).name("赣州市").build(),
TreeNode.builder().id(6).pid(5).name("章贡").build()
);
// 获取树节点
TreeNode root = nodes.stream().filter(node -> Objects.equals(node.getPid(), 0)).findFirst().orElse(null);
if (root == null) {
return null;
}
root.setChildrenNode(getChildren(root, nodes));
return root;
}
/**
* 多个父节点模式
*
* @return
*/
@GetMapping("/manyFather")
public Set<TreeNode> listMany() {
List<TreeNode> nodes = new ArrayList<>();
// 封装数据
Collections.addAll(nodes,
TreeNode.builder().id(1).pid(0).name("江西省").build(),
TreeNode.builder().id(2).pid(1).name("吉安市").build(),
TreeNode.builder().id(3).pid(2).name("遂川县").build(),
TreeNode.builder().id(5).pid(1).name("赣州市").build(),
TreeNode.builder().id(6).pid(5).name("章贡").build(),
TreeNode.builder().id(7).pid(0).name("浙江省").build(),
TreeNode.builder().id(8).pid(7).name("杭州").build()
);
// 封装树节点
Set<TreeNode> root = nodes.stream().filter(node -> Objects.equals(node.getPid(), 0)).collect(Collectors.toSet());
if (CollectionUtils.isEmpty(root)) {
return null;
}
// 遍历树节点
root.stream().forEach(rootNode -> {
rootNode.setChildrenNode(getChildren(rootNode, nodes));
});
return root;
}
/**
* 递归获取根节点的子节点信息
*
* @param root 根节点
* @param nodes 要遍历的节点
* @return
*/
private List<TreeNode> getChildren(TreeNode root, List<TreeNode> nodes) {
List<TreeNode> childrenNodes = nodes.stream()
// 使用stream流过滤出数据的父级id信息等于根节点的id信息的数据
.filter(node -> Objects.equals(node.getPid(), root.getId()))
.map(childNode -> {
// 同时使用map方法对每个过滤后的数据进行处理,递归找到其对应的子数据
childNode.setChildrenNode(getChildren(childNode, nodes));
return childNode;
})
.collect(Collectors.toList());
return childrenNodes;
}
}
测试:
1)单个父节点模式:http://ip:port/treeNode/oneFather
{
"id": 1,
"name": "江西省",
"pid": 0,
"childrenNode": [
{
"id": 2,
"name": "吉安市",
"pid": 1,
"childrenNode": [
{
"id": 3,
"name": "遂川县",
"pid": 2,
"childrenNode": []
}
]
},
{
"id": 5,
"name": "赣州市",
"pid": 1,
"childrenNode": [
{
"id": 6,
"name": "章贡",
"pid": 5,
"childrenNode": []
}
]
}
]
}
2)多个父节点模式:http://ip:port/treeNode/oneFather
[
{
"id": 1,
"name": "江西省",
"pid": 0,
"childrenNode": [
{
"id": 2,
"name": "吉安市",
"pid": 1,
"childrenNode": [
{
"id": 3,
"name": "遂川县",
"pid": 2,
"childrenNode": []
}
]
},
{
"id": 5,
"name": "赣州市",
"pid": 1,
"childrenNode": [
{
"id": 6,
"name": "章贡",
"pid": 5,
"childrenNode": []
}
]
}
]
},
{
"id": 7,
"name": "浙江省",
"pid": 0,
"childrenNode": [
{
"id": 8,
"name": "杭州",
"pid": 7,
"childrenNode": []
}
]
}
]