经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » MySQL » 查看文章
树形结构的菜单表设计与查询
来源:cnblogs  作者:废物大师兄  时间:2021/5/6 17:51:09  对本文有异议

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

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

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

答案是肯定可以的,比如用mongoDB,直接将整棵树存成json。但是,这样不利于按条件查询,当然也取决于具体的需求,抛开需求谈设计都是耍流氓。

在菜单这个场景下,一般还是用关系型数据库存储,可以将最终的查询结构缓存起来。

常用的方法有四种:

  • 每一条记录存parent_id
  • 每一条记录存整个tree path经过的node枚举
  • 每一条记录存 nleft 和 nright
  • 维护一个表,所有的tree path作为记录进行保存

 

第一种:每条记录存储parent_id

这种方式简单明了,但是想要查询某个节点的所有父级和子级的时候比较困难,势必需要用到递归,在mysql里面就得写存储过程,太麻烦了。

当然,如果只有两级的话就比较简单了,自连接就搞定了,例如:

第四种:单独用一种表保存节点之间的关系

  1. CREATE TABLE `city` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT,
  3. `name` varchar(16),
  4. PRIMARY KEY (`id`) USING BTREE
  5. ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4;
  6. CREATE TABLE `city_tree_path_info` (
  7. `id` int(11) NOT NULL AUTO_INCREMENT,
  8. `city_id` int(11) NOT NULL,
  9. `ancestor_id` int(11) NOT NULL COMMENT '祖先ID',
  10. `level` tinyint(4) NOT NULL COMMENT '层级',
  11. PRIMARY KEY (`id`) USING BTREE
  12. ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4;

上面这个例子中,city表代表城市,city_tree_path_info代表城市之间的层级关系,ancestor_id表示父级和祖父级ID,level是当前记录相对于ancestor_id而言的层级。这样就把整个层级关系保存到这张表中了,以后想查询某个节点的所有父级和子级就很容易了。

最后,我发现构造这种层级树最简单的还是用java代码

java递归生成菜单树

Menu.java 

  1. 1 package com.example.demo.model;
  2. 2
  3. 3 import lombok.AllArgsConstructor;
  4. 4 import lombok.Data;
  5. 5 import lombok.NoArgsConstructor;
  6. 6
  7. 7 import java.util.List;
  8. 8
  9. 9 @AllArgsConstructor
  10. 10 @NoArgsConstructor
  11. 11 @Data
  12. 12 public class Menu {
  13. 13
  14. 14 /**
  15. 15 * 菜单ID
  16. 16 */
  17. 17 private Integer id;
  18. 18
  19. 19 /**
  20. 20 * 父级菜单ID
  21. 21 */
  22. 22 private Integer pid;
  23. 23
  24. 24 /**
  25. 25 * 菜单名称
  26. 26 */
  27. 27 private String name;
  28. 28
  29. 29 /**
  30. 30 * 菜单编码
  31. 31 */
  32. 32 private String code;
  33. 33
  34. 34 /**
  35. 35 * 菜单URL
  36. 36 */
  37. 37 private String url;
  38. 38
  39. 39 /**
  40. 40 * 菜单图标
  41. 41 */
  42. 42 private String icon;
  43. 43
  44. 44 /**
  45. 45 * 排序号
  46. 46 */
  47. 47 private int sort;
  48. 48
  49. 49 /**
  50. 50 * 子级菜单
  51. 51 */
  52. 52 private List<Menu> children;
  53. 53
  54. 54 public Menu(Integer id, Integer pid, String name, String code, String url, String icon, int sort) {
  55. 55 this.id = id;
  56. 56 this.pid = pid;
  57. 57 this.name = name;
  58. 58 this.code = code;
  59. 59 this.url = url;
  60. 60 this.icon = icon;
  61. 61 this.sort = sort;
  62. 62 }
  63. 63
  64. 64 }

Test.java

  1. 1 package com.example.demo.model;
  2. 2
  3. 3 import com.fasterxml.jackson.core.JsonProcessingException;
  4. 4 import com.fasterxml.jackson.databind.ObjectMapper;
  5. 5
  6. 6 import java.util.ArrayList;
  7. 7 import java.util.Comparator;
  8. 8 import java.util.List;
  9. 9 import java.util.stream.Collectors;
  10. 10
  11. 11 public class Hello {
  12. 12 public static void main(String[] args) throws JsonProcessingException {
  13. 13 List<Menu> allMenuList = new ArrayList<>();
  14. 14 allMenuList.add(new Menu(1, 0, "湖北", "HuBei", "/a", "a", 3));
  15. 15 allMenuList.add(new Menu(2, 0, "河南", "HeNan", "/b", "b", 2));
  16. 16 allMenuList.add(new Menu(3, 1, "宜昌", "YiChang", "/c", "c", 2));
  17. 17 allMenuList.add(new Menu(4, 2, "信阳", "XinYang", "/d", "d", 1));
  18. 18 allMenuList.add(new Menu(5, 1, "随州", "SuiZhou", "/e", "e", 1));
  19. 19 allMenuList.add(new Menu(6, 5, "随县", "SuiXian", "/f", "f", 2));
  20. 20 allMenuList.add(new Menu(7, 3, "枝江", "ZhiJiang", "/g", "g", 2));
  21. 21
  22. 22 // 一级菜单
  23. 23 List<Menu> parentList = allMenuList.stream().filter(e->e.getPid()==0).sorted(Comparator.comparing(Menu::getSort)).collect(Collectors.toList());
  24. 24 // 递归调用,为所有一级菜单设置子菜单
  25. 25 for (Menu menu : parentList) {
  26. 26 menu.setChildren(getChild(menu.getId(), allMenuList));
  27. 27 }
  28. 28
  29. 29 ObjectMapper objectMapper = new ObjectMapper();
  30. 30 System.out.println(objectMapper.writeValueAsString(parentList));
  31. 31 }
  32. 32
  33. 33 /**
  34. 34 * 递归查找子菜单
  35. 35 * @param id 当前菜单ID
  36. 36 * @param allList 查找菜单列表
  37. 37 * @return
  38. 38 */
  39. 39 public static List<Menu> getChild(Integer id, List<Menu> allList) {
  40. 40 // 子菜单
  41. 41 List<Menu> childList = new ArrayList<>();
  42. 42 for (Menu menu : allList) {
  43. 43 if (menu.getPid().equals(id)) {
  44. 44 childList.add(menu);
  45. 45 }
  46. 46 }
  47. 47
  48. 48 // 为子菜单设置子菜单
  49. 49 for (Menu nav : childList) {
  50. 50 nav.setChildren(getChild(nav.getId(), allList));
  51. 51 }
  52. 52
  53. 53 // 排序
  54. 54 childList = childList.stream().sorted(Comparator.comparing(Menu::getSort)).collect(Collectors.toList());
  55. 55
  56. 56 if (childList.size() == 0) {
  57. 57 // return null;
  58. 58 return new ArrayList<>();
  59. 59 }
  60. 60 return childList;
  61. 61 }
  62. 62 }

结果:

  1. 1 [
  2. 2 {
  3. 3 "id":2,
  4. 4 "pid":0,
  5. 5 "name":"河南",
  6. 6 "code":"HeNan",
  7. 7 "url":"/b",
  8. 8 "icon":"b",
  9. 9 "sort":2,
  10. 10 "children":[
  11. 11 {
  12. 12 "id":4,
  13. 13 "pid":2,
  14. 14 "name":"信阳",
  15. 15 "code":"XinYang",
  16. 16 "url":"/d",
  17. 17 "icon":"d",
  18. 18 "sort":1,
  19. 19 "children":[]
  20. 20 }
  21. 21 ]
  22. 22 },
  23. 23 {
  24. 24 "id":1,
  25. 25 "pid":0,
  26. 26 "name":"湖北",
  27. 27 "code":"HuBei",
  28. 28 "url":"/a",
  29. 29 "icon":"a",
  30. 30 "sort":3,
  31. 31 "children":[
  32. 32 {
  33. 33 "id":5,
  34. 34 "pid":1,
  35. 35 "name":"随州",
  36. 36 "code":"SuiZhou",
  37. 37 "url":"/e",
  38. 38 "icon":"e",
  39. 39 "sort":1,
  40. 40 "children":[
  41. 41 {
  42. 42 "id":6,
  43. 43 "pid":5,
  44. 44 "name":"随县",
  45. 45 "code":"SuiXian",
  46. 46 "url":"/f",
  47. 47 "icon":"f",
  48. 48 "sort":2,
  49. 49 "children":[]
  50. 50 }
  51. 51 ]
  52. 52 },
  53. 53 {
  54. 54 "id":3,
  55. 55 "pid":1,
  56. 56 "name":"宜昌",
  57. 57 "code":"YiChang",
  58. 58 "url":"/c",
  59. 59 "icon":"c",
  60. 60 "sort":2,
  61. 61 "children":[
  62. 62 {
  63. 63 "id":7,
  64. 64 "pid":3,
  65. 65 "name":"枝江",
  66. 66 "code":"ZhiJiang",
  67. 67 "url":"/g",
  68. 68 "icon":"g",
  69. 69 "sort":2,
  70. 70 "children":[]
  71. 71 }
  72. 72 ]
  73. 73 }
  74. 74 ]
  75. 75 }
  76. 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 

 

原文链接:http://www.cnblogs.com/cjsblog/p/14714444.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号