业务场景
通常我们前端需要一个树形的导航菜单或者分类菜单,如后台权限管理中的权限树,亦或者下面例子中商城系统的商品分类多级菜单(一般为三级菜单)
数据库设计
数据库设计,采用parentId
来指向自己的父级菜单,如:
CREATE TABLE `pms_category` ( `cat_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类id', `name` char(50) DEFAULT NULL COMMENT '分类名称', `parent_cid` bigint(20) DEFAULT NULL COMMENT '父分类id', `cat_level` int(11) DEFAULT NULL COMMENT '层级', `show_status` tinyint(4) DEFAULT NULL COMMENT '是否显示[0-不显示,1显示]', `sort` int(11) DEFAULT NULL COMMENT '排序', `icon` char(255) DEFAULT NULL COMMENT '图标地址', `product_unit` char(50) DEFAULT NULL COMMENT '计量单位', `product_count` int(11) DEFAULT NULL COMMENT '商品数量', PRIMARY KEY (`cat_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1433 DEFAULT CHARSET=utf8mb4 COMMENT='商品三级分类';
java组装树形结构
之前的做法是通过sql自连接来查出树形结构数据,但是效率不高,我们知道单表查询效率是最高的,我们可以一次查出所有数据,通过java8
的新特性 stream
来处理数据,stream
是通过CPU
计算实现,效率极高,具体用法可以参考:
Java 8新特性之 Lambd和StreamAPI
下面是处理数据的两个主要方法:
@Override public List<CategoryEntity> listWithTree() { // 1. 先查出所有分类数据 List<CategoryEntity> categories = baseMapper.selectList(null); // 2. 找出所有一级分类 // 在映射到每个一级分类 添加它的子分类类 return categories.stream() .filter(o -> o.getParentCid() == 0) // 给每个一级分类加子分类 .peek(o -> o.setChildrens(getChildCategoryList(o, categories))) // 排序 .sorted(Comparator.comparingInt(CategoryEntity::getSort)) // 收集 .collect(Collectors.toList()); } // 根据当前分类 找出子类, 并通过递归找出子类的子类 private List<CategoryEntity> getChildCategoryList(CategoryEntity currMenu, List<CategoryEntity> categories) { return categories.stream().filter(o -> o.getParentCid().equals(currMenu.getCatId())) .peek(o -> o.setChildrens(getChildCategoryList(o, categories))) .sorted(Comparator.comparingInt(CategoryEntity::getSort)) .collect(Collectors.toList()); }
实体类变动
- 为了拼接子菜单,需要将实体类增加一个属性childrens
- 排序时需要用到sort属性,该字段在数据库可能为null,采用三元运算将其默认为0,防止排序异常
@TableField(exist = false) private List<CategoryEntity> childrens; public Integer getSort() { return sort == null ? 0 : sort; }
返回数据效果