2021年4月28日星期三

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

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

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

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

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

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

常用的方法有四种:

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

 

第一种:每条记录存储parent_id

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

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

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

CREATE TABLE `city` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(16), PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER 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 = 1 CHARACTER SET = utf8mb4;

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

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

java递归生成菜单树

Menu.java 

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

Test.java

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

参考:

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.shaoqun.com/a/712741.html

跨境电商:https://www.ikjzd.com/

周宁:https://www.ikjzd.com/w/1647

邮乐:https://www.ikjzd.com/w/1776


开发中经常会遇到树形结构的场景,比如:导航菜单、组织机构等等,但凡是有这种父子层级结构的都是如此,一级类目、二级类目、三级类目。。。对于这种树形结构的表要如何设计呢?接下来一起探讨一下首先,想一个问题,用非关系型数据库存储可不可以?答案是肯定可以的,比如用mongoDB,直接将整棵树存成json。但是,这样不利于按条件查询,当然也取决于具体的需求,抛开需求谈设计都是耍流氓。在菜单这个场景下,一般还
r标:https://www.ikjzd.com/w/1070
商标抢注:https://www.ikjzd.com/w/1053
东西网:https://www.ikjzd.com/w/1238
麒麟云:https://www.ikjzd.com/w/2588
亚马逊《品牌加速器计划》:https://www.ikjzd.com/tl/99929
口述:我妈做了一件事打击了婆婆的嚣张气焰:http://lady.shaoqun.com/m/a/95659.html

没有评论:

发表评论