项目中免不了会遇到树形结构的查询,今天分享一下我写的一个查询方法。 先看数据库的数据,主要有id,parentId,name三个字段(可能还有其他本次无用的很多字段),根节点的父id设置为0
实现的效果
[{
"children": [{
"children": [{
"children": [{
"children": [],
"id": 11,
"name": "四级目录1111",
"parentId": 5
}],
"id": 5,
"name": "三级目录111",
"parentId": 3
}, {
"children": [],
"id": 6,
"name": "三级目录112",
"parentId": 3
}, {
"children": [],
"id": 7,
"name": "三级目录113",
"parentId": 3
}],
"id": 3,
"name": "二级目录11",
"parentId": 1
}],
"id": 1,
"name": "一级目录1",
"parentId": 0
}, {
"children": [{
"children": [{
"children": [{
"children": [],
"id": 12,
"name": "四级目录2111",
"parentId": 8
}],
"id": 8,
"name": "三级目录211",
"parentId": 4
}, {
"children": [],
"id": 9,
"name": "三级目录212",
"parentId": 4
}, {
"children": [],
"id": 10,
"name": "三级目录213",
"parentId": 4
}],
"id": 4,
"name": "二级目录21",
"parentId": 2
}],
"id": 2,
"name": "一级目录2",
"parentId": 0
}]
下面是业务层的实现代码,主要是递归的思想
package yulisao.test;
import com.alibaba.fastjson.JSON;
import yulisao.bean.Menu;
import yulisao.bean.MenuTree;
import java.util.ArrayList;
import java.util.List;
/**
* @author yulisao
* @createDate 2022/7/9 0009 下午 3:54
*/
public class MenuTreeTest {
public static void main(String[] args) {
List<MenuTree> resp = getMenuTreeList(0L); // c从根节点开始查找。根节点的id是0
System.out.println(JSON.toJSONString(resp));
}
public static List<MenuTree> getMenuTreeList(Long parentId) {
List<MenuTree> menuTrees = new ArrayList<>();
//List<Menu> menuList = xxxxxMapper.findByParentId(parentId); // 去数据库根据父id查询
List<Menu> menuList = findByParentId(parentId);
for (Menu menu : menuList) {
MenuTree nodeTree = new MenuTree();
nodeTree.setId(menu.getId());
nodeTree.setParentId(menu.getParentId());
nodeTree.setName(menu.getName());
nodeTree.setChildren(getMenuTreeList(menu.getId()));//递归
menuTrees.add(nodeTree);
}
return menuTrees;
}
/**
* 模拟 根据父id从数据库查询数据
* @param parentId
* @return
*/
public static List<Menu> findByParentId (Long parentId) {
List<Menu> menus = new ArrayList<>();
if (parentId==0) {
Menu menu = new Menu();
menu.setId(1L);
menu.setParentId(0L);
menu.setName("一级目录1");
menus.add(menu);
Menu menu1 = new Menu();
menu1.setId(2L);
menu1.setParentId(0L);
menu1.setName("一级目录2");
menus.add(menu1);
}
if (parentId==1) {
Menu menu = new Menu();
menu.setId(3L);
menu.setParentId(1L);
menu.setName("二级目录11");
menus.add(menu);
}
if (parentId==2) {
Menu menu = new Menu();
menu.setId(4L);
menu.setParentId(2L);
menu.setName("二级目录21");
menus.add(menu);
}
if (parentId==3) {
Menu menu = new Menu();
menu.setId(5L);
menu.setParentId(3L);
menu.setName("三级目录111");
menus.add(menu);
Menu menu1 = new Menu();
menu1.setId(6L);
menu1.setParentId(3L);
menu1.setName("三级目录112");
menus.add(menu1);
Menu menu2 = new Menu();
menu2.setId(7L);
menu2.setParentId(3L);
menu2.setName("三级目录113");
menus.add(menu2);
}
if (parentId==4) {
Menu menu = new Menu();
menu.setId(8L);
menu.setParentId(4L);
menu.setName("三级目录211");
menus.add(menu);
Menu menu1 = new Menu();
menu1.setId(9L);
menu1.setParentId(4L);
menu1.setName("三级目录212");
menus.add(menu1);
Menu menu2 = new Menu();
menu2.setId(10L);
menu2.setParentId(4L);
menu2.setName("三级目录213");
menus.add(menu2);
}
if (parentId==5) {
Menu menu = new Menu();
menu.setId(11L);
menu.setParentId(5L);
menu.setName("四级目录1111");
menus.add(menu);
}
if (parentId==8) {
Menu menu = new Menu();
menu.setId(12L);
menu.setParentId(8L);
menu.setName("四级目录2111");
menus.add(menu);
}
return menus;
}
}
实体和返回对象
package yulisao.bean;
/**
* 菜单实体对象
* @author yulisao
* @createDate 2022/7/9 0009 下午 5:16
*/
public class Menu {
private Long id;
private Long parentId;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package yulisao.bean;
import java.util.List;
/**
* 返回的菜单树对象 相比菜单实体对象这里只取了我们需要返回给前端的部分字段,并且多了一个list成员来存放子节点菜单
* @author yulisao
* @createDate 2022/7/9 0009 下午 3:55
*/
public class MenuTree {
private Long id;
private Long parentId;
private String name;
List<MenuTree> children;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<MenuTree> getChildren() {
return children;
}
public void setChildren(List<MenuTree> children) {
this.children = children;
}
}
创建表语句
CREATE TABLE `t_menu` (
`id` INT(11) NOT NULL,
`parentId` INT(11) NOT NULL,
`url` VARCHAR(50) NOT NULL,
`menuname` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=gbk COMMENT '菜单表';
INSERT INTO `t_menu` (`id`, `parentId`, `url`, `menuname`) VALUES('1','0','yourUrl','一级目录1');
INSERT INTO `t_menu` (`id`, `parentId`, `url`, `menuname`) VALUES('2','0','yourUrl','一级目录2');
INSERT INTO `t_menu` (`id`, `parentId`, `url`, `menuname`) VALUES('3','1','yourUrl','二级目录11');
INSERT INTO `t_menu` (`id`, `parentId`, `url`, `menuname`) VALUES('4','2','yourUrl','二级目录21');
INSERT INTO `t_menu` (`id`, `parentId`, `url`, `menuname`) VALUES('5','3','yourUrl','三级目录111');
INSERT INTO `t_menu` (`id`, `parentId`, `url`, `menuname`) VALUES('6','3','yourUrl','三级目录112');
INSERT INTO `t_menu` (`id`, `parentId`, `url`, `menuname`) VALUES('7','3','yourUrl','三级目录113');
INSERT INTO `t_menu` (`id`, `parentId`, `url`, `menuname`) VALUES('8','4','yourUrl','三级目录211');
INSERT INTO `t_menu` (`id`, `parentId`, `url`, `menuname`) VALUES('9','4','yourUrl','三级目录212');
INSERT INTO `t_menu` (`id`, `parentId`, `url`, `menuname`) VALUES('10','4','yourUrl','三级目录213');
INSERT INTO `t_menu` (`id`, `parentId`, `url`, `menuname`) VALUES('11','5','yourUrl','四级目录1111');
INSERT INTO `t_menu` (`id`, `parentId`, `url`, `menuname`) VALUES('12','8','yourUrl','四级目录2111');
分析一下getMenuTreeList方法,先查出父id为0的全部数据(即一级菜单),遍历一级菜单得到一级菜单的id后,在根据这个id去查二级菜单的数据,一层一层往下查。查不到的时候menuList为空也就不会进入for循环了自然不会无限循环。另外一种写法和这个类似,就是不带任何条件先查询出所有的数据,然后从所有数据中去判断他们的父id是不是我要找的,若是则添加。
public static List<MenuTree> getMenuTreeList2(Long parentId,List<Menu> allMenuList) {
List<MenuTree> menuTrees = new ArrayList<>();
for (Menu menu : allMenuList) {
//遍历所有数据,如果该数据的父id等于当前要找的id就添加
if (parentId.equals(menu.getParentId())) {
MenuTree nodeTree = new MenuTree();
nodeTree.setId(menu.getId());
nodeTree.setParentId(menu.getParentId());
nodeTree.setName(menu.getName());
nodeTree.setChildren(getMenuTreeList2(menu.getId(), allMenuList)); // 递归
menuTrees.add(nodeTree);
}
}
return menuTrees;
}
二者的区别就是只查一次数据库和多次的区别,具体选哪个视菜单表的数据量有多少以及他的层级有多深。数据量不大层级不深,推荐用后者,一次查询把数据全部加载到内存中来,减少数据库连接的开销。