树形结构的菜单表设计与查询
开发中经常会遇到树形结构的场景,⽐如:导航菜单、组织机构等等,但凡是有这种⽗⼦层级结构的都是如此,⼀级类⽬、⼆级类⽬、三级类⽬。。。
对于这种树形结构的表要如何设计呢?接下来⼀起探讨⼀下
⾸先,想⼀个问题,⽤⾮关系型数据库存储可不可以?
答案是肯定可以的,⽐如⽤mongoDB,直接将整棵树存成json。但是,这样不利于按条件查询,当然也取决于具体的需求,抛开需求谈设计都是耍流氓。
在菜单这个场景下,⼀般还是⽤关系型数据库存储,可以将最终的查询结构缓存起来。
常⽤的⽅法有四种:
每⼀条记录存parent_id
每⼀条记录存整个tree path经过的node枚举
每⼀条记录存 nleft 和 nright
维护⼀个表,所有的tree path作为记录进⾏保存
第⼀种:每条记录存储parent_id
这种⽅式简单明了,但是想要查询某个节点的所有⽗级和⼦级的时候⽐较困难,势必需要⽤到递归,在mysql⾥⾯就得写存储过程,太⿇烦了。
当然,如果只有两级的话就⽐较简单了,⾃连接就搞定了,例如:
第四种:单独⽤⼀种表保存节点之间的关系
CREATE TABLE `city`  (
sortedlist`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(16),
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT =1CHARACTER SET= utf8mb4;
CREATE TABLE `city_tree_path_info`  (
`id` int(11) NOT NULL AUTO_INCREMENT,
`city_id` int(11) NOT NULL,
`ancestor_id` int(11) NOT NULL COMMENT '祖先ID',
`level` tinyint(4) NOT NULL COMMENT '层级',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT =1CHARACTER SET= utf8mb4;
上⾯这个例⼦中,city表代表城市,city_tree_path_info代表城市之间的层级关系,ancestor_id表⽰⽗级和祖⽗级ID,level是当前记录相对于ancestor_id⽽⾔的层级。这样就把整个层级关系保存到这张表中了,以后想查询某个节点的所有⽗级和⼦级就很容易了。
最后,我发现构造这种层级树最简单的还是⽤java代码
java递归⽣成菜单树
Menu.java
del;
2
3import lombok.AllArgsConstructor;
4import lombok.Data;
5import lombok.NoArgsConstructor;
6
7import java.util.List;
8
9@AllArgsConstructor
10@NoArgsConstructor
11@Data
12public class Menu{
13
14/**
15    * 菜单ID
16    */
17private Integer id;
18
19/**
20    * ⽗级菜单ID
21    */
22private Integer pid;
23
24/**
25    * 菜单名称
26    */
27private String name;
28
29/**
30    * 菜单编码
31    */
32private String code;
33
34/**
35    * 菜单URL
36    */
37private String url;
38
39/**
40    * 菜单图标
41    */
42private String icon;
43
44/**
45    * 排序号
46    */
47private int sort;
48
49/**
50    * ⼦级菜单
51    */
52private List<Menu>children;
53
54public Menu(Integer id,Integer pid,String name,String code,String url,String icon,int sort){
55this.id=id;
56this.pid=pid;
57this.name=name;
59this.url=url;
60this.icon=icon;
61this.sort=sort;
62}
63
64}
Test.java
del;
2
3import com.JsonProcessingException;
4import com.fasterxml.jackson.databind.ObjectMapper;
5
6import java.util.ArrayList;
7import java.util.Comparator;
8import java.util.List;
9import java.util.stream.Collectors;
10
11public class Hello{
12public static void main(String[]args)throws JsonProcessingException{
13List<Menu>allMenuList=new ArrayList<>();
14allMenuList.add(new Menu(1,0,"湖北","HuBei","/a","a",3));
15allMenuList.add(new Menu(2,0,"河南","HeNan","/b","b",2));
16allMenuList.add(new Menu(3,1,"宜昌","YiChang","/c","c",2));
17allMenuList.add(new Menu(4,2,"信阳","XinYang","/d","d",1));
18allMenuList.add(new Menu(5,1,"随州","SuiZhou","/e","e",1));
19allMenuList.add(new Menu(6,5,"随县","SuiXian","/f","f",2));
20allMenuList.add(new Menu(7,3,"枝江","ZhiJiang","/g","g",2));
21
22//  ⼀级菜单
23List<Menu>parentList=allMenuList.stream().filter(e-&Pid()==0).sorted(Comparatorparing(
Menu::getSort)).List()); 24//  递归调⽤,为所有⼀级菜单设置⼦菜单
25for(Menu menu:parentList){
26menu.setChildren(Id(),allMenuList));
27}
28
29ObjectMapper objectMapper=new ObjectMapper();
30System.out.println(objectMapper.writeValueAsString(parentList));
31}
32
33/**
34    * 递归查⼦菜单
35    * @param id    当前菜单ID
36    * @param allList  查菜单列表
37    * @return
38    */
39public static List<Menu>getChild(Integer id,List<Menu>allList){
40//  ⼦菜单
41List<Menu>childList=new ArrayList<>();
42for(Menu menu:allList){
Pid().equals(id)){
44childList.add(menu);
45}
46}
47
48//  为⼦菜单设置⼦菜单
49for(Menu nav:childList){
50nav.setChildren(Id(),allList));
51}
52
53//  排序
54childList=childList.stream().sorted(Comparatorparing(Menu::getSort)).List()); 55
56if(childList.size()==0){
57//            return null;
58return new ArrayList<>();
59}
60return childList;
61}
62}
结果:
1[
2{
3"id":2,
4"pid":0,
5"name":"河南",
6"code":"HeNan",
7"url":"/b",
8"icon":"b",
9"sort":2,
10"children":[
11{
12"id":4,
13"pid":2,
14"name":"信阳",
15"code":"XinYang",
16"url":"/d",
17"icon":"d",
18"sort":1,
19"children":[]
20}
21]
22},
23{
24"id":1,
25"pid":0,
26"name":"湖北",
27"code":"HuBei",
28"url":"/a",
29"icon":"a",
30"sort":3,
31"children":[
32{
33"id":5,
34"pid":1,
35"name":"随州",
36"code":"SuiZhou",
37"url":"/e",
38"icon":"e",
39"sort":1,
40"children":[
41{
42"id":6, 43"pid":5, 44"name":"随县", 45"code":"SuiXian", 46"url":"/f", 47"icon":"f", 48"sort":2, 49"children":[]
50}
51]
52},
53{
54"id":3,
55"pid":1, 56"name":"宜昌", 57"code":"YiChang", 58"url":"/c", 59"icon":"c", 60"sort":2, 61"children":[
62{
63"id":7, 64"pid":3, 65"name":"枝江", 66"code":"ZhiJiang", 67"url":"/g", 68"icon":"g", 69"sort":2, 70"children":[]
71}
72]
73}
74]
75}
76]
参考:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。