树形结构的菜单表设计与查询
开发中经常会遇到树形结构的场景,比如:导航菜单、组织机构等等,但凡是有这种父子层级结构的都是如此,一级类目、二级类目、三级类目。。。
对于这种树形结构的表要如何设计呢?接下来一起探讨一下
首先,想一个问题,用非关系型数据库存储可不可以?
答案是肯定可以的,比如用mongoDB,直接将整棵树存成json。但是,这样不利于按条件查询,当然也取决于具体的需求,抛开需求谈设计都是耍流氓。
在菜单这个场景下,一般还是用关系型数据库存储,可以将最终的查询结构缓存起来。
常用的方法有四种:
- 每一条记录存parent_id
- 每一条记录存整个tree path经过的node枚举
- 每一条记录存 nleft 和 nright
- 维护一个表,所有的tree path作为记录进行保存
第一种:每条记录存储parent_id
这种方式简单明了,但是想要查询某个节点的所有父级和子级的时候比较困难,势必需要用到递归,在mysql里面就得写存储过程,太麻烦了。
当然,如果只有两级的话就比较简单了,自连接就搞定了,例如:
第四种:单独用一种表保存节点之间的关系
CREATETABLE `city` (`id`
int(11) NOTNULL AUTO_INCREMENT,`name`
varchar(16),PRIMARYKEY (`id`) USING BTREE) ENGINE
= InnoDB AUTO_INCREMENT =1CHARACTERSET= utf8mb4;CREATETABLE `city_tree_path_info` (`id`
int(11) NOTNULL AUTO_INCREMENT,`city_id`
int(11) NOTNULL,`ancestor_id`
int(11) NOTNULL COMMENT "祖先ID",`
level` tinyint(4) NOTNULL COMMENT "层级",PRIMARYKEY (`id`) USING BTREE) ENGINE
= InnoDB AUTO_INCREMENT =1CHARACTERSET= utf8mb4;
上面这个例子中,city表代表城市,city_tree_path_info代表城市之间的层级关系,ancestor_id表示父级和祖父级ID,level是当前记录相对于ancestor_id而言的层级。这样就把整个层级关系保存到这张表中了,以后想查询某个节点的所有父级和子级就很容易了。
最后,我发现构造这种层级树最简单的还是用java代码
java递归生成菜单树
Menu.java
1packagecom.example.demo.model; 2
3importlombok.AllArgsConstructor;
4importlombok.Data;
5importlombok.NoArgsConstructor;
6
7importjava.util.List;
8
9@AllArgsConstructor
10@NoArgsConstructor
11@Data
12publicclassMenu{
13
14/**
15 * 菜单ID
16 */
17privateIntegerid;
18
19/**
20 * 父级菜单ID
21 */
22privateIntegerpid;
23
24/**
25 * 菜单名称
26 */
27privateStringname;
28
29/**
30 * 菜单编码
31 */
32privateStringcode;
33
34/**
35 * 菜单URL
36 */
37privateStringurl;
38
39/**
40 * 菜单图标
41 */
42privateStringicon;
43
44/**
45 * 排序号
46 */
47privateintsort;
48
49/**
50 * 子级菜单
51 */
52privateList<Menu>children;
53
54publicMenu(Integerid,Integerpid,Stringname,Stringcode,Stringurl,Stringicon,intsort){
55this.id=id;
56this.pid=pid;
57this.name=name;
58this.code=code;
59this.url=url;
60this.icon=icon;
61this.sort=sort;
62}
63
64}
Test.java
1packagecom.example.demo.model; 2
3importcom.fasterxml.jackson.core.JsonProcessingException;
4importcom.fasterxml.jackson.databind.ObjectMapper;
5
6importjava.util.ArrayList;
7importjava.util.Comparator;
8importjava.util.List;
9importjava.util.stream.Collectors;
10
11publicclassHello{
12publicstaticvoidmain(String[]args)throwsJsonProcessingException{
13List<Menu>allMenuList=newArrayList<>();
14allMenuList.add(newMenu(1,0,"湖北","HuBei","/a","a",3));
15allMenuList.add(newMenu(2,0,"河南","HeNan","/b","b",2));
16allMenuList.add(newMenu(3,1,"宜昌","YiChang","/c","c",2));
17allMenuList.add(newMenu(4,2,"信阳","XinYang","/d","d",1));
18allMenuList.add(newMenu(5,1,"随州","SuiZhou","/e","e",1));
19allMenuList.add(newMenu(6,5,"随县","SuiXian","/f","f",2));
20allMenuList.add(newMenu(7,3,"枝江","ZhiJiang","/g","g",2));
21
22// 一级菜单
23List<Menu>parentList=allMenuList.stream().filter(e->e.getPid()==0).sorted(Comparator.comparing(Menu::getSort)).collect(Collectors.toList());
24// 递归调用,为所有一级菜单设置子菜单
25for(Menumenu:parentList){
26menu.setChildren(getChild(menu.getId(),allMenuList));
27}
28
29ObjectMapperobjectMapper=newObjectMapper();
30System.out.println(objectMapper.writeValueAsString(parentList));
31}
32
33/**
34 * 递归查找子菜单
35 * @param id 当前菜单ID
36 * @param allList 查找菜单列表
37 * @return
38 */
39publicstaticList<Menu>getChild(Integerid,List<Menu>allList){
40// 子菜单
41List<Menu>childList=newArrayList<>();
42for(Menumenu:allList){
43if(menu.getPid().equals(id)){
44childList.add(menu);
45}
46}
47
48// 为子菜单设置子菜单
49for(Menunav:childList){
50nav.setChildren(getChild(nav.getId(),allList));
51}
52
53// 排序
54childList=childList.stream().sorted(Comparator.comparing(Menu::getSort)).collect(Collectors.toList());
55
56if(childList.size()==0){
57// return null;
58returnnewArrayList<>();
59}
60returnchildList;
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]
参考:
https://www.cnblogs.com/w2206/p/10490208.html
https://www.cnblogs.com/mokingone/p/9109021.html
https://www.cnblogs.com/makai/p/12301707.html
https://www.cnblogs.com/zhifengge/p/6910881.html
以上是 树形结构的菜单表设计与查询 的全部内容, 来源链接: utcz.com/z/535582.html