递归树形结构的多级分类数据封装

2023-10-29

         在日常开发中,我们经常需要查询一些树形结构多级分类数据,如:多级菜单、商品三级分类、企业组织架构等等。

        我们以商品三级分类为例。大部分情况下,在同一张数据表中,无论是一级商品还是三级商品,每一条商品信息独占一行空间,通过“层级”字段,标明该商品属于第几级,通过“父id”字段,判断该商品是否被其他商品包括其中,如:

但是,客户端请求分级数据,如果我们直接将表中的数据通过“select * from t_table”的方式响应给前端,前端将很难将其转化成三级分类的效果并展示出来,通常,我们会将查表的数据做层级处理,再将这种分层的数据响应给前端,如:

       

         所以,我们对于这种查询分级信息通常的处理方式是:先在实体类中添加一个属性List<T>,用来封装该商品的子类商品集合:

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<CategoryEntity> children;

}

首先,查表获得所有商品信息,再遍历集合,过滤出所有一级商品,然后,通过递归的方式, 将子类商品按照分类等级封装到一级商品实体类的List<T>集合中。

        事实上,所有的多级分类信息我们都是通过递归查找然后将组装好的树形结构信息响应给前端。问题在于,再一个项目中,用到这种分级查询的情况还挺多的,比如:多级菜单、商品三级分类、企业组织架构等等。如果每个用到该功能的地方都写一遍代码,确实会造成一些代码的冗余。那么,我们能否尝试将递归封装的过程抽取出来,创建一个静态方法,当我程序中需要用到分级信息封装的时候,只需要调用方法,将查表得到的list传递过去,获得封装好的分级对象呢?这里,我做了一个尝试:

        第一步:创建一个“基础树形结构”类BaseTreeVo<T>

/**
 * 基础树形结构 vo类
 *
 * @param <T> 泛型
 * @author tg
 */
@Data
public class BaseTreeVo<T> {
    /**
     * 主键id
     */
    private Long catId;
    /**
     * 父id
     */
    private Long parentCid;
    /**
     * 子类集合
     */
    private List<T> children = new ArrayList<T>();
}

        第二步:创建一个要响应给前端的vo类,该vo类继承BaseTreeVo<T>

/**
 * 商品分类信息 vo类
 *
 * @author gaozz
 */
@Data
public class CategoryVo extends BaseTreeVo<CategoryVo> {
    ...
}

        第三步:将查表得到的list<entity>转换成list<vo>

// 1、查出表中所有商品分类信息
List<CategoryEntity> categories = baseMapper.selectList(null);
// 封装商品分类信息
List<CategoryVo> collect = categories.stream().map(CategoryVo::new).collect(Collectors.toList());

        第四步:调用树形结构工具类的方法,将第三步得到的list<vo>传递过去,进行递归封装。在工具类中,事先封装好了一个静态方法:

/**
 * 树形结构工具
 *
 * @author tg
 * @date 2022-11-01 23:16:24
 */
public class BaseTreeUtils {

    /**
     * 获取树形结构
     *
     * @param allList 树形列表
     * @param <T>     泛型
     * @return 树形结构
     */
    public static <T extends BaseTreeVo> List<T> listTreeNodes(List<T> allList) {
        // 创建一级分类集合
        List<T> 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 <T>          泛型
     * @return 树形结构
     */
    public static <T extends BaseTreeVo> List<T> recursiveTree(T parentNode, List<T> classifyList) {
        List<T> 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)方法,实现功能,也提高了程序的复用性。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

递归树形结构的多级分类数据封装 的相关文章

  • JDK 文档是语言规范的一部分吗?

    只有一名官员Java语言规范 https docs oracle com javase specs jls se8 html index html所有 Java 实现都必须遵守它 API文档怎么样 所有Java实现都需要遵守吗这个版本 ht
  • 如何将 javax.persistence.Column 定义为 Unsigned TINYINT?

    我正在基于 MySQL 数据库中的现有表创建 Java 持久性实体 Bean 使用 NetBeans IDE 8 0 1 我在这个表中遇到了一个字段 其类型为 无符号 TINYINT 3 我发现可以执行以下操作将列的类型定义为 unsign
  • Java:无法从同一包中的不同类访问静态变量

    这很奇怪 因为我有一个可以访问 Frame dimension getWidth 的 Character 类 及其伙伴 getHeight 但是当我想在 Map 类中使用它时 Eclipse 强调了它并且无法给我反馈 运行该程序最终会出现
  • 使用 Ant 将非代码资源添加到 jar 文件

    我正在将 java 应用程序打包成 jar 文件 我正在使用 ant 和 eclipse 我实际上需要在 jar 中直接在根文件夹下包含几个单独的非代码文件 xml 和 txt 文件 而不是与代码位于同一位置 我正在尝试使用includes
  • 使用 GWT 读取非常大的本地 XML 文件

    我正在使用 GWT 构建我的第一个 Java 应用程序 它必须从一个非常大的 XML 文件中读取数据 当我尝试发送对文件中信息的请求时遇到问题 并且我不太确定它是否与文件的大小或我的语义有关 在我的程序中 我有以下内容 static fin
  • 如何在 Antlr4 中为零参数函数编写语法

    我的函数具有参数语法 如下面的词法分析器和解析器 MyFunctionsLexer g4 lexer grammar MyFunctionsLexer FUNCTION FUNCTION NAME A Za z0 9 DOT COMMA L
  • Java:在 eclipse 中导出到 .jar 文件

    我正在尝试将 Eclipse 中的程序导出到 jar 文件 在我的项目中 我添加了一些图片和 PDF s 当我导出到 jar 文件时 似乎只有main已编译并导出 我的意愿是如果可能的话将所有内容导出到 jar 文件 因为这样我想将其转换为
  • 不同类型的数组

    是否可以有一个包含两种不同类型数据的数组 我想要一个包含双精度型和字符串的数组 我尝试过 ArrayList
  • Spring Boot自动装配存储库始终为空[重复]

    这个问题在这里已经有答案了 每次我进入我的服务类时 存储库似乎都没有自动连接 因为它不断抛出 NullPointerException 谁能帮我检查一下我缺少什么吗 这是我的代码 演示应用程序 java package com exampl
  • 通往楼梯顶部的可能路径

    这是一个非常经典的问题 我听说谷歌在他们的面试中使用过这个问题 问题 制定一个递归方法 打印从楼梯底部到楼梯顶部的所有可能的独特路径 有 n 个楼梯 您一次只能走 1 步或 2 步 示例输出 如果它是一个有 3 级楼梯的楼梯 1 1 1 2
  • 了解joda时间PeriodFormatter

    我以为我明白了 但显然我不明白 你能帮我通过这些单元测试吗 Test public void second assertEquals 00 00 01 OurDateTimeFormatter format 1000 Test public
  • HashMap 值需要不可变吗?

    我知道 HashMap 中的键需要是不可变的 或者至少确保它们的哈希码 hashCode 不会改变或与另一个具有不同状态的对象发生冲突 但是 HashMap中存储的值是否需要与上面相同 为什么或者为什么不 这个想法是能够改变值 例如在其上调
  • 在 SWT/JFace RCP 应用程序中填充巨大的表

    您将如何在 SWT 表中显示大量行 巨大是指超过 20K 行 20 列的东西 不要问我为什么需要展示那么多数据 这不是重点 关键是如何让它尽可能快地工作 这样最终用户就不会厌倦等待 每行显示某个对象的实例 列是其属性 一些 我想使用 JFa
  • 如何在 Java 中创建接受多个值的单个注释

    我有一个名为 Retention RetentionPolicy SOURCE Target ElementType METHOD public interface JIRA The Key Bug number JIRA referenc
  • Java Swing:需要一个高质量的带有复选框的开发 JTree

    我一直在寻找一个 Tree 实现 其中包含复选框 其中 当您选择一个节点时 树中的所有后继节点都会被自动选择 当您取消选择一个节点时 树中其所有后继节点都会自动取消选择 当已经选择了父节点 并且从其后继之一中删除了选择时 节点颜色将发生变化
  • OpenCSV:将嵌套 Bean 映射到 CSV 文件

    我正在尝试将 bean 映射到 CSV 文件 但问题是我的 bean 具有其他嵌套 bean 作为属性 所发生的情况是 OpenCSV 遍历属性找到一个 bean 然后进入其中并映射该 bean 内的所有数据 如果找到另一个 bean 它就
  • 如何重新启动死线程? [复制]

    这个问题在这里已经有答案了 有哪些不同的可能性可以带来死线程回到可运行状态 如果您查看线程生命周期图像 就会发现一旦线程终止 您就无法返回到新位置 So 没有办法将死线程恢复到可运行状态 相反 您应该创建一个新的 Thread 实例
  • org.apache.commons.net.io.CopyStreamException:复制时捕获 IOException

    我正在尝试使用以下方法中的代码将在我的服务器中创建的一些文件复制到 FTP 但奇怪的是我随机地低于错误 我无法弄清楚发生了什么 Exception org apache commons net io CopyStreamException
  • 将 Apache Camel 执行器指标发送到 Prometheus

    我正在尝试转发 添加 Actuator Camel 指标 actuator camelroutes 将交换 交易数量等指标 发送到 Prometheus Actuator 端点 有没有办法让我配置 Camel 将这些指标添加到 Promet
  • 泛型、数组和 ClassCastException

    我想这里一定发生了一些我不知道的微妙事情 考虑以下 public class Foo

随机推荐

  • 我决定豁出去了,公开我做过的人工智能实战项目核心技术,有没有你心动的?

    前言 博主一直没有公开多少人工智能项目代码 但不少粉丝和朋友都很好奇真实人工智能项目到底是怎样的 我思前想后 打算针对几块我做过的人工智能项目给大家分享下 不过由于公司这两年发展比较快 项目方向有点多 不知道小伙伴们具体感兴趣哪块呢 欢迎大
  • UE4C++中如何申明“TSubclassOf”型数组

    之前查了查居然没人回答过 不废话 直接看代码 UPROPERTY EditDefaultsOnly TArray
  • C/C++面试:引用和指针的使用场合

    问 指针和引用作用都是间接引用其他对象 你如何决定何时使用指针 何时使用引用呢 应该使用指针的场合 有指向不存在对象的可能时 在任何情况下都不能使用指向空值的引用 一个引用必须总是指向某些对象 因此 如果你使用一个变量时并让它指向一个对象
  • 块设备、字符设备、裸设备和文件系统个人总结

    1 块设备 系统中可以随机访问 不需要按顺序 访问固定大小数据片 chunks 的设备称为块设备 这些数据片就称作块 硬盘是最常见的块设备 除此以外 还有软盘驱动器 CD ROM驱动器和闪存等 这里要注意 它们都是以安装文件系统的方式使用的
  • LeetCode Roman to Integer(罗马数字转换)

    思路 罗马数字是 阿拉伯数字传入之前使用的一种数码 罗马数字采用七个罗马字母作数字 即 1 X 10 C 100 M 1000 V 5 L 50 D 500 记数的方法 相同的数字连写 所表示的数等于这些数字相加得到的数 如 3 小的数字在
  • STC89C52系列单片机内部资源——中断系统

    中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的 中断功能的存在 很大程度上提高了单片机处理外部或者内部事件的能力 它也是单片机最重要的功能之一 是我们学习单片机必须要掌握 首先介绍单片机中断技术的优点 解决了快速主机与慢速I
  • FastGPT 接入飞书(不用写一行代码)

    FastGPT V4 版本已经发布 可以通过 Flow 可视化进行工作流编排 从而实现复杂的问答场景 例如联网谷歌搜索 操作数据库等等 功能非常强大 还没用过的同学赶紧去试试吧 飞书相比同类产品算是体验非常好的办公工具了 我司也是废了很大的
  • 操作系统学习(三)基本分段存储管理方式

    一 分段的定义 进程的地址空间按照自身的逻辑关系划分为若干段 例如 主程序 两个子程序 栈和一段数据 把进程分成5段 每段从0进行编址 段间要求连续 段内不要求 二 段表 1 分段系统的逻辑地址结构由段号 段名 和段内地址 段内偏移量 组成
  • uni-app中自定义动态底部tabbar(附示例源码)

    UNIAPP 自带的原生导航尽管流畅度非常好 但是在具体项目中有的时候需要动态设置以及特殊样式的 底部菜单 这个时候就需要自己去写一个自定义的底部tabbar 项目地址 fr uni app 1 比如需要特殊的图标 多出来一部分的 2 根据
  • 每日30条知识点-软件设计师知识点笔记

    立即寻址最快 寄存器寻址次之 直接寻址最慢 RISC 精简指令集计算机 特点 指令数量少 寻址方式少 长度固定 格式种类少 只提供load store指令访问存储器 以硬布线逻辑控制为主 单周期指令执行 系统总线用于主存以及外设部件连接 R
  • 数据结构和算法--树

    数据结构和算法是一种思想 理解了思想就是忘记了代码也能找回原来的记忆 二叉搜索树 二叉树 每个结点只存储一个关键字 等于则命中 小于走左结点 大于走右结点 AVL树 每个节点的左子树和右子树的高度最多差1的二叉搜索树 B B 树 多路搜索树
  • C++(26)——对象被优化以后才是最高效的C++编程

    对象应用优化 我们都知道 C语言和C 在程序执行中 都是通过调用一系列的函数来实现的 并且 很多时候 编译器会帮助我们做一系列的事情 比如 在编译类的成员方法的时候 编译器默认添加 this 指针 以此来确定是哪一个对象调用了该成员方法 得
  • C#上位机串口控制12864显示

    实现的效果 上面是用Proteus仿真的 对了如果自己想用proteus仿真需要安装下面这个软件 再看一下实物显示效果 先做上位机部分 为了程序一启动就把电脑上能用的串口号显示在下拉框中 private void Form1 Load ob
  • android使用Glide加载RelativeLayout、LinearLayout等背景图片

    转载请注明出处 http blog csdn net ym4189 article details 78712256 Glide框架大家应该都很熟悉 我们可以使用Glide加载网络图片 加载gif图片 使用简单 一般情况下我们都是使用Gli
  • 解决 adbd cannot run as root in production builds 问题

    这学期的一个android实验遇到的问题 经过借鉴多位大佬的文章已经解决 我的情况是android studio 不能很好的连接goole浏览器 所以不能下载其他版本 看到其他方法 https blog csdn net w69033324
  • 如何解决(变量或函数)重复定义的问题?

    错误提示 Objects light 1 axf Error L6200E Symbol gpio init multiply defined by gpio o and main o Not enough information to l
  • mongoose 实现DBRef查找所有子类信息

    产品表 var Mongoose require mongoose var Schema Mongoose Schema var Product new Schema image type String description type S
  • 【highlight】highlight 动态添加代码,样式失效

    在使用highlight 的时候 发现效果不错 然而当我用jQuery改变内容时 酷炫的高亮效果没了 原来hljs initHighlightingOnLoad 初始化的时候只会对其渲染一次 查阅资料发现 解决方案 code code 使用
  • s3 实现图片上传并返回图片路径

    一般做图片上传的的业务时我们接收的都是 MultipartFile 类型的文件 但是s3提供的图片上传只支持file 类型的文件上传 所以需要我先把 MultipartFile 转换为 file MultipartFile 转换为file
  • 递归树形结构的多级分类数据封装

    在日常开发中 我们经常需要查询一些树形结构的多级分类数据 如 多级菜单 商品三级分类 企业组织架构等等 我们以商品三级分类为例 大部分情况下 在同一张数据表中 无论是一级商品还是三级商品 每一条商品信息独占一行空间 通过 层级 字段 标明该