需求:生活中常见页面一次性展示一级商品、二级商品、三级商品…甚至更多级别商品
大家通常会想到数据库一张表设计商品分类,有等级level字段,parent_id父id等关键设计字段
实体类也通常设计本实体关联子集的List
@TableName("item_cat")
@Data
@Accessors(chain = true)
public class ItemCat extends BasePojo{
@TableId(type = IdType.AUTO)
private Integer id; //定义主键
private Integer parentId; //定义父级菜单
private String name; //分类名称
private Boolean status; //分类状态 0 停用 1 正常
private Integer level; //商品分类等级 1 2 3
@TableField(exist = false)
private List<ItemCat> children;
}
但java后台代码返回的lsit怎么包含各层级商品分类结构呢?
假设这里商品等级最多三级, 相信很多人首先想到的思路是:
先查询出一级商品,然后遍历一级商品,拿着一级商品的id去数据库查二级商品,然后又依次遍历二级商品,拿着二级商品的id去数据库查三级商品,常规代码如下
/**
* 3层商品分类嵌套 1一级分类(children(2级商品分类))
* 2一级分类(children(3级商品分类))
* 一级查询条件 parent_id=0
* 二级查询条件 parent_id=一级的ID
* 三级查询条件 parent_id=二级的ID
*/
@Override
public List<ItemCat> findItemCatList(Integer level) {
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("parent_id", 0);
List<ItemCat> list = itemCatMapper.selectList(queryWrapper);
for (ItemCat oneItemCat : list) {
queryWrapper.clear();
queryWrapper.eq("parent_id", oneItemCat.getId());
List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
oneItemCat.setChildren(twoList);
for (ItemCat children : twoList) {
queryWrapper.clear();
queryWrapper.eq("parent_id", children.getId());
children.setChildren(itemCatMapper.selectList(queryWrapper));
}
}
return list;
}
大家想过没有,以上代码执行效率非常低下,最大问题在于for循环的嵌套,嵌套太多层导致程序运行速度慢,占用内存高。假设一级商品有500种,对应的二级商品又有500种,这样至少查询数据库500*500=25000次,与数据库交互的频次太多,导致性能出现问题,所以以上代码非常不可取。
现要求,只查询一次数据库,即可获取3级商品分类信息。
大家想到思路没?没有想到的可以学习以下设计思路和代码,想到啦,看看以下思路是否是同一种思路,有好的建议,欢迎分享
设计
- 查询数据库中的所有的数据信息
- key: parentId value: 当前parentId下的所有的子级 封装成Map集合
- 将查询的结果封装到Map集合中
- 如需获取子级数据信息,通过getKey 即可获取数据
代码如下
/**
* 1.准备Map集合,实现数据封装 Map<Key,Value> Map<parentId,List<ItemCat>>
* 2.业务说明
* map中的key~~~parentId 不存在 可以存储该key 设定key,同时封装一个list集合,将自己作为第一个元素封装到其中
* 存在 根据key获取所有子集集合,将自己追加进去,形成第二个元素
*/
public Map<Integer, List<ItemCat>> itemCatMap() {
Map<Integer, List<ItemCat>> map = new HashMap<>();
//1.查询所有的数据库信息
List<ItemCat> list = itemCatMapper.selectList(null);
for (ItemCat itemCat : list) {
int parentId = itemCat.getParentId();
if (map.containsKey(parentId)) {//有key 获取list集合 将自己追加到集合中
List<ItemCat> exeList = map.get(parentId);//引用对象,无需在put
exeList.add(itemCat);
} else {//没有key--将自己封装为第一个list元素
List<ItemCat> firstList = new ArrayList<>();
firstList.add(itemCat);
map.put(parentId, firstList);
}
}
return map;
}
@Override
public List<ItemCat> findItemCatList(Integer level) {
Map<Integer, List<ItemCat>> map = itemCatMap();
//1.如果level=1 说明获取一级商品分类信息 parent_id=0
if (level == 1) {
return map.get(0);
}
if (level == 2) {//2.获取二级商品信息,
return getTwoList(map);
}
//3.获取三级商品信息
//3.1 获取二级商品分类信息
List<ItemCat> oneList = getTwoList(map);
for (ItemCat oneItemCat : oneList) {
List<ItemCat> twoList = oneItemCat.getChildren();
//如果该元素没有2级列表,则跳过本次循环
if (twoList == null || twoList.size() == 0) {
continue;
}
for (ItemCat twoItemCat : twoList) {
//查询三级商品分类 条件parentId=2级id
twoItemCat.setChildren(map.get(twoItemCat.getId()));
}
}
return oneList;
}
/**
* 获取二级商品分类信息(注:二级嵌套在一级集合中,所以永远返回的都是第一级)
*/
public List<ItemCat> getTwoList(Map<Integer, List<ItemCat>> map) {
List<ItemCat> oneList = map.get(0);
for (ItemCat oneItemCat : oneList) {//查询二级 parentId=1级id
List<ItemCat> twoList = map.get(oneItemCat.getId());
oneItemCat.setChildren(twoList);
}
//二级嵌套在一级集合中,所以永远返回的都是一级
return oneList;
}
如果支持无线级层呢,可看下来逻辑
public class MyTest {
public static void main(String[] args) {
List<TreeNode> singleNodes = new ArrayList<>();
singleNodes.add(new TreeNode("a", "1", "节点1"));
singleNodes.add(new TreeNode("b", "2", "节点2"));
singleNodes.add(new TreeNode("4", "9", "节点1-1-1"));
singleNodes.add(new TreeNode("c", "3", "节点3"));
singleNodes.add(new TreeNode("2", "8", "节点2-2"));
singleNodes.add(new TreeNode("1", "4", "节点1-1"));
singleNodes.add(new TreeNode("9", "10", "节点1-1-1-1"));
singleNodes.add(new TreeNode("2", "5", "节点2-1"));
singleNodes.add(new TreeNode("1", "6", "节点1-3"));
singleNodes.add(new TreeNode("1", "7", "节点1-2"));
List<TreeNode> treeNodes = assmTree(singleNodes);
System.out.println(JSON.toJSON(treeNodes));
}
/**
* 组装树结构数据的方法
*/
public static List<TreeNode> assmTree(List<TreeNode> singleTreeNodes) {
// 判断排序数据是否为空
if (singleTreeNodes == null || singleTreeNodes.isEmpty()) {
return null;
}
// 用有序Map把传参组装起来
Map<String, TreeNode> nodeId2treeNodes = singleTreeNodes.stream().collect(Collectors.toMap(TreeNode::getNodeId, node -> node));
// 用来保存组装好的数据, 作为返回值
List<TreeNode> topNodeTrees = new ArrayList<>();
// 遍历所有节点
for (String nodeId : nodeId2treeNodes.keySet()) {
TreeNode treeNode = nodeId2treeNodes.get(nodeId);
String pid = treeNode.getPid();
// 当前节点的父id为空 或 整个列表中没有以此父id作为自己的id的情况
// 即当前节点是根节点
if (pid == null || pid.length() == 0 || !nodeId2treeNodes.containsKey(pid)) {
// treeNode.setPid(""); // 此处可以根据需要调整父id的值
topNodeTrees.add(treeNode);
} else { // 不是父节点, 作为子节点添加进去
TreeNode parentTreeNode = nodeId2treeNodes.get(pid);
if (parentTreeNode.getChildren() == null) {
parentTreeNode.setChildren(new ArrayList<>());
}
parentTreeNode.getChildren().add(treeNode);
}
}
return topNodeTrees;
}
}
@Data
@NoArgsConstructor
class TreeNode {
/**
* 父节点ID
*/
private String pid;
/**
* 节点ID
*/
private String nodeId;
/**
* 节点名称
*/
private String nodeName;
/**
* 子节点
*/
private List<TreeNode> children;
public TreeNode(String pid, String nodeId, String nodeName) {
this.nodeId = nodeId;
this.pid = pid;
this.nodeName = nodeName;
}
}