需求场景
有下面一张菜单表,典型的树形结构设计
现前端需要后端返回树形数据结构用于构造展示树。
代码实战
- 首先我们根据数据库结构创建实体对象
@Data
@TableName("tb_menu")
public class Menu extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 菜单ID
*/
@TableId
private Long menuId;
/**
* 菜单名称
*/
private String menuName;
/**
* 父菜单名称
*/
@TableField(exist = false)
private String parentName;
/**
* 父菜单ID
*/
private Long parentId;
/**
* 显示顺序
*/
private String orderNum;
/**
* 路由地址
*/
private String path;
/**
* 组件路径
*/
private String component;
/**
* 是否为外链(0是 1否)
*/
private String isFrame;
/**
* 是否缓存(0缓存 1不缓存)
*/
private String isCache;
/**
* 类型(M目录 C菜单 F按钮)
*/
private String menuType;
/**
* 显示状态(0显示 1隐藏)
*/
private String visible;
/**
* 菜单状态(0显示 1隐藏)
*/
private String status;
/**
* 权限字符串
*/
private String perms;
/**
* 菜单图标
*/
private String icon;
/**
* 子菜单
*/
@TableField(exist = false)
private List<Menu> children = new ArrayList<Menu>();
最重要的还是id、parentId、children属性
最通用的写法(递归)
这种写法毫无节操可言,全部通过数据库递归查询。
- 首先查到根节点,parent_id = 0
- 通过根节点id获取到所有一级节点,parent_id = 1
- 递归获取所有节点的子节点,然后调用setChildren()方法组装数据结构。
好多项目都是用这种方法。
1、递归:
/**
* 根据父节点的id获取所有子节点
*
* @param menus
* @return
*/
private List<Menu> getChildPerm(List<Menu> menus, int parentId) {
List<Menu> menuList = new ArrayList<>();
//当parentId = 0时,是根节点;
// 获取根节点下的子节点
for (Menu menu : menus) {
if (menu.getParentId() == parentId) {
Menu menuData = this.recursionTree(menus, menu);
menuList.add(menuData);
}
}
return menuList;
}
```
/**
* 递归树结构
*
* @param collect
*/
private Menu recursionTree(List<Menu> collect, Menu menu) {
List<Menu> childList = this.getChildList(collect, menu);
menu.setChildren(childList);
for (Menu col : childList) {
if (hasChild(collect, col)) {
recursionTree(collect, col);
}
}
return menu;
}
**
* 得到子节点列表
*/
private List<Menu> getChildList(List<Menu> list, Menu t) {
List<Menu> tlist = new ArrayList<Menu>();
Iterator<Menu> it = list.iterator();
while (it.hasNext()) {
Menu n = (Menu) it.next();
if (n.getParentId().longValue() == t.getMenuId().longValue()) {
tlist.add(n);
}
}
return tlist;
}
这样比较啰嗦,性能不是很好,逻辑也不是很清楚。
2、双重循环:
这种写法比较简单,也是比较容易想到的。通过双重循环确定父子节点的关系。
/**
* 获取菜单的树状结构
*
* @return
*/
private List<Menu> menuTrees() {
List<Menu> list = new ArrayList<>();
List<Menu> menus = menuMapper.selectList(null);
for (Menu m : menus) {
//获取根节点
if (m.getParentId().equals(0L)) {
list.add(m);
}
for (Menu child : menus) {
if (child.getParentId().equals(m.getMenuId())) {
m.addChild(child);
}
}
}
return list;
}
//菜单的实体类中加addChild方法
public void addChild(Menu menu) {
if (children == null) {
children = new ArrayList<>();
}
children.add(menu);
}
3、双重遍历
第一次遍历借助hashmap存储父节点与子节点的关系,第二次遍历设置子节点,由于map中已经维护好了对应关系所以只需要从map取即可。
/**
* 双重查询
*
* @return
*/
private List<Menu> menuList() {
Map<Long, List<Menu>> menuMap = new HashMap<>();
List<Menu> menuList = menuMapper.selectList(null);
menuList.forEach(menu -> {
List<Menu> children = menuMap.getOrDefault(menu.getParentId(), new ArrayList<>());
children.add(menu);
menuMap.put(menu.getParentId(), children);
});
menuList.forEach(menu -> menu.setChildren(menuMap.get(menu.getMenuId())));
List<Menu> result = menuList.stream().filter(v -> v.getParentId().equals("0")).collect(Collectors.toList());
return result;
}
4、Stream分组
/**
* Stream分组
*/
@Override
public List<Menu> selectMenuTree() {
List<Menu> menus = menuMapper.selectList(null);
//操作所有菜单数据
Map<Long, List<Menu>> groupMap = menus.stream().collect(Collectors.groupingBy(Menu::getParentId));
menus.forEach(menu -> {
menu.setChildren(groupMap.get(menu.getMenuId()));
});
List<Menu> collect = menus.stream().filter(menu -> menu.getParentId().equals(0L)).collect(Collectors.toList());
return collect;
}
此方法主要通过Collectors.groupingBy(Menu::getParentId)
方法对menus
按照parentId
进行分组,分组后父节点相同的都放一起了。
然后再循环menus
,给其设置children属性。
执行完成后已经形成了多颗树,最后我们再通过filter()
方法挑选出根节点的那颗树即可。
请求后返回的数据集如下:
"code": 200,
"data": [
{
"searchValue": null,
"createBy": "admin",
"createTime": "2021-03-10 16:30:56",
"updateBy": "",
"updateTime": null,
"remark": "系统管理目录",
"params": null,
"menuId": 1,
"menuName": "系统管理",
"parentName": null,
"parentId": 0,
"orderNum": "1",
"path": "system",
"component": null,
"isFrame": "1",
"isCache": "0",
"menuType": "M",
"visible": "0",
"status": "0",
"perms": "",
"icon": "system",
"children": [
{
"searchValue": null,
"createBy": "admin",
"createTime": "2021-03-10 16:30:56",
"updateBy": "",
"updateTime": null,
"remark": "用户管理菜单",
"params": null,
"menuId": 100,
"menuName": "用户管理",
"parentName": null,
"parentId": 1,
"orderNum": "1",
"path": "user",
"component": "system/user/index",
"isFrame": "1",
"isCache": "0",
"menuType": "C",
"visible": "0",
"status": "0",
"perms": "system:user:list",
"icon": "user",
"children": [
{
"searchValue": null,
"createBy": "admin",
"createTime": "2021-03-10 16:31:00",
"updateBy": "",
"updateTime": null,
"remark": "",
"params": null,
"menuId": 1001,
"menuName": "用户查询",
"parentName": null,
"parentId": 100,
"orderNum": "1",
"path": "",
"component": "",
"isFrame": "1",
"isCache": "0",
"menuType": "F",
"visible": "0",
"status": "0",
"perms": "system:user:query",
"icon": "#",
"children": null
},
{
"searchValue": null,
"createBy": "admin",
"createTime": "2021-03-10 16:31:00",
"updateBy": "",
"updateTime": null,
"remark": "",
"params": null,
"menuId": 1002,
"menuName": "用户新增",
"parentName": null,
"parentId": 100,
"orderNum": "2",
"path": "",
"component": "",
"isFrame": "1",
"isCache": "0",
"menuType": "F",
"visible": "0",
"status": "0",
"perms": "system:user:add",
"icon": "#",
"children": null
},
{
"searchValue": null,
"createBy": "admin",
"createTime": "2021-03-10 16:31:01",
"updateBy": "",
"updateTime": null,
"remark": "",
"params": null,
"menuId": 1003,
"menuName": "用户修改",
"parentName": null,
"parentId": 100,
"orderNum": "3",
"path": "",
"component": "",
"isFrame": "1",
"isCache": "0",
"menuType": "F",
"visible": "0",
"status": "0",
"perms": "system:user:edit",
"icon": "#",
"children": null
}
]
}
]
}
]
}