需求:生活中常见页面一次性展示一级商品、二级商品、三级商品…甚至更多级别商品

大家通常会想到数据库一张表设计商品分类,有等级level字段,parent_id父id等关键设计字段

java商品管理项目 java怎样实现商品分类_java


实体类也通常设计本实体关联子集的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级商品分类信息。
大家想到思路没?没有想到的可以学习以下设计思路和代码,想到啦,看看以下思路是否是同一种思路,有好的建议,欢迎分享

设计

  1. 查询数据库中的所有的数据信息
  2. key: parentId value: 当前parentId下的所有的子级 封装成Map集合
  3. 将查询的结果封装到Map集合中
  4. 如需获取子级数据信息,通过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;
  }

}