在日常开发中,我们经常需要查询一些树形结构的多级分类数据,如:多级菜单、商品三级分类、企业组织架构等等。
我们以商品三级分类为例。大部分情况下,在同一张数据表中,无论是一级商品还是三级商品,每一条商品信息独占一行空间,通过“层级”字段,标明该商品属于第几级,通过“父id”字段,判断该商品是否被其他商品包括其中,如:
但是,客户端请求分级数据,如果我们直接将表中的数据通过“select * from t_table”的方式响应给前端,前端将很难将其转化成三级分类的效果并展示出来,通常,我们会将查表的数据做层级处理,再将这种分层的数据响应给前端,如:
所以,我们对于这种查询分级信息通常的处理方式是:先在实体类中添加一个属性List
public class CategoryEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 分类id
*/
@TableId
private Long catId;
/**
* 分类名称
*/
private String name;
/**
* 父分类id
*/
private Long parentCid;
/**
* 层级
*/
private Integer catLevel;
/**
* 子菜单
*/
@TableField(exist = false)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List children;
}
首先,查表获得所有商品信息,再遍历集合,过滤出所有一级商品,然后,通过递归的方式, 将子类商品按照分类等级封装到一级商品实体类的List
事实上,所有的多级分类信息我们都是通过递归查找然后将组装好的树形结构信息响应给前端。问题在于,再一个项目中,用到这种分级查询的情况还挺多的,比如:多级菜单、商品三级分类、企业组织架构等等。如果每个用到该功能的地方都写一遍代码,确实会造成一些代码的冗余。那么,我们能否尝试将递归封装的过程抽取出来,创建一个静态方法,当我程序中需要用到分级信息封装的时候,只需要调用方法,将查表得到的list传递过去,获得封装好的分级对象呢?这里,我做了一个尝试:
第一步:创建一个“基础树形结构”类BaseTreeVo
/**
* 基础树形结构 vo类
*
* @param 泛型
* @author tg
*/
@Data
public class BaseTreeVo {
/**
* 主键id
*/
private Long catId;
/**
* 父id
*/
private Long parentCid;
/**
* 子类集合
*/
private List children = new ArrayList();
}
第二步:创建一个要响应给前端的vo类,该vo类继承BaseTreeVo
/**
* 商品分类信息 vo类
*
* @author gaozz
*/
@Data
public class CategoryVo extends BaseTreeVo {
...
}
第三步:将查表得到的list
// 1、查出表中所有商品分类信息
List categories = baseMapper.selectList(null);
// 封装商品分类信息
List collect = categories.stream().map(CategoryVo::new).collect(Collectors.toList());
第四步:调用树形结构工具类的方法,将第三步得到的list
/**
* 树形结构工具
*
* @author tg
* @date 2022-11-01 23:16:24
*/
public class BaseTreeUtils {
/**
* 获取树形结构
*
* @param allList 树形列表
* @param 泛型
* @return 树形结构
*/
public static List listTreeNodes(List allList) {
// 创建一级分类集合
List parentList = new ArrayList<>();
if (!CollectionUtils.isEmpty(allList)) {
// 过滤出所有的一级分类
parentList = allList.stream().filter(item -> item.getParentCid() == 0).collect(Collectors.toList());
}
//返回的树形节点数据
if (!CollectionUtils.isEmpty(parentList)) {
for (T parentNode : parentList) {
//递归查询所有子节点
parentNode.setChildren(recursiveTree(parentNode, allList));
}
}
return parentList;
}
/**
* 递归算法解析成树形结构
*
* @param parentNode 父级对象
* @param classifyList 集合
* @param 泛型
* @return 树形结构
*/
public static List recursiveTree(T parentNode, List classifyList) {
List childList = new ArrayList<>();
//子集的直接子对象
for (T entity : classifyList) {
Long parentId = entity.getParentCid();
if (parentNode.getCatId() == parentId) {
childList.add(entity);
}
}
//子集的间接子对象
for (T entity : childList) {
entity.setChildren(recursiveTree(entity, classifyList));
}
//递归退出条件
if (childList.size() == 0) {
return null;
}
return childList;
}
}
该方法的逻辑是:先遍历集合,获取所有的一级分类,然后遍历获得到的一级分类集合,再一级分类信息中再递归查找是否有子分类属于该一级分类,如果有,按照层级放到对应分类的子类集合children中。
最后,运行程序,得到了我们预期想要的结果:
如此,我们便实现了递归树形结构的多级分类数据封装,当我们需要用到分类信息显示时,可以调用BaseTreeUtils.listTreeNodes(collect)方法,实现功能,也提高了程序的复用性。