优雅的使用 Stream 流和 Lambda 组装复杂父子树形结构

全新前后端微商城项目,手把手教学!

全新前后端微商城项目,手把手教学!

全新前后端微商城项目,手把手教学!

前言

在最近的开发中,遇到了两个类似的需求:都是基于 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 List<DepartmentVO> childrenList;
    /**
     * 部门Id
     */
    protected Integer deptId;
    /**
     * 父部门Id
     */
    protected Integer parentDeptId;
    /**
     * 部门名称
     */
    protected String name;
}
1.3具体实现

下面直接上 demo 代码,注释已经说的比较清楚了:

@Override
public List<DepartmentVO> departmentStructure(String id){
        //step1:这里 map 只是简单转换了返回的对象属性(返回需要的类型),本质还是所有部门数据
        List<DepartmentVO> departmentVOList = this.getDepartmentListById(id).stream()
                .map(e -> e.copyProperties(DepartmentVO.class))
                .collect(Collectors.toList());
        //step2:利用父节点分组,所有部门的父 Id 进行分组,把所有的子节点 List 集合都找出来并一层层分好组
        Map<Integer, List<DepartmentVO>> departmentListMap = departmentVOList.stream()
                .collect(Collectors.groupingBy(DepartmentVO::getParentDeptId));
        //step3:关键一步,关联上子部门,将子部门的 List 集合经过遍历一层层地放置好,最终会得到完整的部门父子关系 List 集合
        departmentVOList.forEach(e -> e.setChildrenList(departmentListMap.get(e.getDeptId())));
        //step4:过滤出顶级部门,即所有的子部门数据都归属于一个顶级父部门 Id
        List<DepartmentVO> 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消耗)可以在一秒内完成。

48e37a23411b2db9fe1c6fac6fa122bb.png
部门结构效果图

二、以中国行政区域结构为例

实体只需要使用一次查全量的语句,没有其它别的操作,很大程度上是因为省市县的结构是比较固定的。

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 List<AreaVO> 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 List<AreaVO> getAreaStructure() {
    //第一步,从数据库中查出所有数据,按照排序条件进行排序,本质上还是这个所有数据的 List 集合
    List<AreaVO> areaVOList = this.findAll(Sort.by("id").descending()).stream()
            //注:这里使用 map 映射了需要返回的 VO,即相同的属性字段就会转换
            .map(e -> e.copyProperties(AreaVO.class)).collect(Collectors.toList());
    if (CollectionUtils.isNotEmpty(areaVOList)){
        //第二步,根据父Id 字段进行分组,即所有数据都会按照第一层至最后一层都按照父子关系进行分组;注意,是对所有数据分组
        Map<Long, List<AreaVO>> areaVoListMap = areaVOList.parallelStream().collect(Collectors.groupingBy(AreaVO::getParentId));
        //第三步,也是最关键的一步,将所有子数据 List 集合经过遍历后都一层层地放置好,最终会得到一个包含父子关系的完整List
        areaVOList.forEach(e -> e.setChildrenAreaVOList(areaVoListMap.get(e.getId())));
        //第四步,过滤出符合顶层父Id的所有数据,即所有数据都归属于一个顶层父Id
        List<AreaVO> 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消耗)可以在一秒内完成。

f54b8d7d706a1e892c004422f259e999.png
中国行政区域信息层次结构效果

时间消耗,这里响应只有两百多毫秒,如下图的接口的性能展示:

a0cd24e48f2dc53e7f443dffae0a414b.png
接口性能展示

三、文章小结

使用 Stream 流组装复杂父子树形结构(List 集合形式)的分享到这里就结束了,编码没有捷径,都是项目实践里出真知,一点点摸索攒经验。

如有不足和错误,或者你有更好的解决思路,欢迎大家的指正和交流!

感谢阅读,希望对你有所帮助 :)   来源:

cnblogs.com/CodeBlogMan/p/17965824

推荐全新学习项目
全新基于springboot+vue+vant的前后端分离的微商城项目,包括手机端微商城项目和后台管理系统,整个电商购物流程已经能流畅支持,涵盖商品浏览、搜索、商品评论、商品规格选择、加入购物车、立即购买、下单、订单支付、后台发货、退货等。功能强大,主流技术栈,非常值得学习。
线上演示:https://www.markerhub.com/vueshop

从文档到视频、接口调试、学习看板等方面,让项目学习更加容易,内容更加沉淀。全套视频教程约35小时,共222期,讲解非常详细细腻。下面详细为大家介绍:
架构与业务使用主流的技术架构,真正手把手教你从0到1如何搭建项目手脚架、项目架构分析、建表逻辑、业务分析、实现等。涵盖SpringBoot、Mybatis Plus、Jwt、Redis、Lombok、Hutool、Shiro、Elasticsearch、RabbitMq、Docker、Jenkins等技术。
vueshop微商城的整个购物流程已经完善,各个模块的业务都是已经实现,涵盖商品模块、搜索模块、购物车模块、订单模块、退款模块、后台权限模块、业务数据管理模块等

更多详情请查看:
手把手教学,从0开发前后端微商城项目,主流Java技术一网打尽!

手把手教学,从0开发前后端微商城项目,主流Java技术一网打尽!

手把手教学,从0开发前后端微商城项目,主流Java技术一网打尽!
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值