电子商务网站建设概括,付费小说网站怎么做,食品分类目录,有经验的赣州网站建设在最近的开发中#xff0c;遇到了两个类似的需求#xff1a;都是基于 Stream 的父子树形结构操作#xff0c;返回 List 集合对象给前端。于是在经过需求分析和探索实践后有了新的认识#xff0c;现在拿出来和大家作分享交流。
一般来说完成这样的需求大多数人会想到递归遇到了两个类似的需求都是基于 Stream 的父子树形结构操作返回 List 集合对象给前端。于是在经过需求分析和探索实践后有了新的认识现在拿出来和大家作分享交流。
一般来说完成这样的需求大多数人会想到递归但递归的方式弊端过于明显方法多次自调用效率很低、数据量大容易导致堆栈溢出、随着树深度的增加其时间复杂度会呈指数级增加等。
核心思路如下
数据库全量查询几万条内存中使用 stream 流操作、Lambda 表达式、Java 地址引用
使用缓存注解底层Redis分布式缓存实现过期后自动更新缓存再次调用接口则先命中缓存没有的话再查数据库
使用 MQ 来做异步通知更新即当数据有更改时可以异步将数据先更新再写入缓存使业务更合理考虑更全面
一、以企业部门结构为例 这里的实体是放在 MySQL 里的使用简单的封装好的查询语句这个很简单。
1.1实体 部门表一个公司里都会有许多的部门一个部门里还会有部门。从最顶层到你所在的的部门可能会有多达六、七层。以下只展示核心字段
Data
public class Department {/*** 主键Id*/private Integer id;/*** 该部门的父部门Id*/private Integer parentDeptId;/*** 真正部门Id*/private Integer deptId;/*** 部门的名称*/private String name;/*** 部门在结构中所处的层级*/private Integer level;/*** 状态是否启用*/private Integer status;
}1.2返回VO 这个返回的VO是给前端的里面的子节点集合属性 childrenList是一个关键字段所有该方式返回树结构的 VO 都需要有该字段来”封装自己“。
Data
public class DepartmentVO implements Serializable {/*** 子节点集合封装自己*/private ListDepartmentVO childrenList;/*** 部门Id*/protected Integer deptId;/*** 父部门Id*/protected Integer parentDeptId;/*** 部门名称*/protected String name;
}1.3具体实现 下面直接上 demo 代码注释已经说的比较清楚了
Override
public ListDepartmentVO departmentStructure(String id){//step1这里 map 只是简单转换了返回的对象属性返回需要的类型本质还是所有部门数据ListDepartmentVO departmentVOList this.getDepartmentListById(id).stream().map(e - e.copyProperties(DepartmentVO.class)).collect(Collectors.toList());//step2利用父节点分组所有部门的父 Id 进行分组把所有的子节点 List 集合都找出来并一层层分好组MapInteger, ListDepartmentVO departmentListMap departmentVOList.stream().collect(Collectors.groupingBy(DepartmentVO::getParentDeptId));//step3关键一步关联上子部门将子部门的 List 集合经过遍历一层层地放置好最终会得到完整的部门父子关系 List 集合departmentVOList.forEach(e - e.setChildrenList(departmentListMap.get(e.getDeptId())));//step4过滤出顶级部门即所有的子部门数据都归属于一个顶级父部门 IdListDepartmentVO resultList departmentVOList.stream().filter(e - Constants.TOP_DEPARTMENT_NUM.equals(e.getParentDeptId())).collect(Collectors.toList());return Optional.of(resultList).orElse(null);
}1.4效果展示 我这里测试的例子是只有三层数据也没有完全展开当然五六层也是没问题的。
只要总的部门数据量在一两万条以内啥情况部门数量会有几万个部门表一般是独立于其它表的速度都是比较快的服务器性能主要内存给力好的话基本整个请求/响应抛开网络I/O消耗可以在一秒内完成。 二、以中国行政区域结构为例 实体只需要使用一次查全量的语句没有其它别的操作很大程度上是因为省市县的结构是比较固定的。
2.1实体 全国行政区表全国的行政区包括省/直辖市/自治区、地级市、区/县级市/县这三级再往下的街道/镇、以及下面的村/小组就不包含了。同样也是只留关键属性
Data
public class Area {/*** 地区id*/public Long id;/*** 父Id*/public Long parentId;/*** 地区名称*/public String name;/*** 所属省Id*/public Long provinceId;/*** 所属地级市Id*/public Long cityId;/*** 所处层级*/public Integer level;
}2.2返回VO 同样这个里面的子节点集合属性 childrenAreaVOList是一个关键字段所有该方式返回树结构的 VO 都需要有该字段来”封装自己“。
Data
public class AreaVO {/*** 子节点 list 集合*/private ListAreaVO childrenAreaVOList;/*** 区域id*/public Long id;/*** 地区名称*/public String name;/*** 所处层级*/public Integer level;/*** 父Id*/public Long parentId;/*** 所属省Id*/public Long provinceId;/*** 所属地级市Id*/public Long cityId;
}2.3具体实现 下面同样直接上 demo 代码注释比较详细
Override
public ListAreaVO getAreaStructure() {//第一步从数据库中查出所有数据按照排序条件进行排序本质上还是这个所有数据的 List 集合ListAreaVO areaVOList this.findAll(Sort.by(id).descending()).stream()//注这里使用 map 映射了需要返回的 VO即相同的属性字段就会转换.map(e - e.copyProperties(AreaVO.class)).collect(Collectors.toList());if (CollectionUtils.isNotEmpty(areaVOList)){//第二步根据父Id 字段进行分组即所有数据都会按照第一层至最后一层都按照父子关系进行分组注意是对所有数据分组MapLong, ListAreaVO areaVoListMap areaVOList.parallelStream().collect(Collectors.groupingBy(AreaVO::getParentId));//第三步也是最关键的一步将所有子数据 List 集合经过遍历后都一层层地放置好最终会得到一个包含父子关系的完整ListareaVOList.forEach(e - e.setChildrenAreaVOList(areaVoListMap.get(e.getId())));//第四步过滤出符合顶层父Id的所有数据即所有数据都归属于一个顶层父IdListAreaVO resultList areaVOList.stream().filter(e - Constants.COUNTRY_CHINA_TOP_NUM.equals(e.getParentId())).collect(Collectors.toList());return Optional.of(resultList).orElse(null);}return new ArrayList();
}2.4效果展示 我这里测试环境的例子是只有省/直辖市/自治区、地级市、区/县级市/县这三级数据也没有完全展开当然到下面的镇/街道乃至村/小组也是没问题的。
这里总的测试数据量是几千条如果加上镇/街道应该得有几万条速度也还是是比较快的服务器性能主要内存给力好的话基本整个请求/响应抛开网络I/O消耗可以在一秒内完成。