树形结构的菜单表设计与查询

database

开发中经常会遇到树形结构的场景,比如:导航菜单、组织机构等等,但凡是有这种父子层级结构的都是如此,一级类目、二级类目、三级类目。。。

对于这种树形结构的表要如何设计呢?接下来一起探讨一下

首先,想一个问题,用非关系型数据库存储可不可以?

答案是肯定可以的,比如用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

回到顶部