项目实战——获取树形结构

简介: 项目实战——获取树形结构

获取树形结构


一、背景介绍

我们在开发中时常会遇到需要用到树形结构这种表示层级关系的结构来表示数据,层级关系明确用户能够很直观的理解彼此的关系。

例如:

本篇博客主要是总结从一张字段具有父子关系的表中查询并将其转换成树形结构通常需要怎么做,具体步骤是什么?以此来解决后序需要类似的问题如何快速实现或者是有思路,将经历转变为自己的经验。

二、 思路和方案

目前一共有两种实现的方案,方案一:使用递归查询的方式并构建树形结构;方案二:使用临时表的方式构建树形结构。

方案一:使用递归查询的方式并构建树形结构

通过递归查询的方式,从根节点开始递归查询所有子节点,然后将他们组成一个树形结构。

宏观步骤:

  1. 通过一次查询获取整个表中的数据。
  2. 再从查询出的数据中筛选出根节点。
  1. 从根节点开始查询所有子节点,再对于每个子节点,在递归查询他们所有子节点
    并将它们组成一个子树。
  2. 将所有子树组成一个完整的属性结构。

方案二:使用临时表的方式构建树形结构

下面是列举的一个示例,起到提供思路的目的。这块儿内容并没有进行实践

宏观步骤:

1.创建临时表temp_tree,用来存储树形结构的各个节点,包括节点id、节点名称、父节点id和层级等信息。

CREATE TEMPORARY TABLE temp_tree (
  id INT PRIMARY KEY,
  name VARCHAR(255),
  parent_id INT,
  level INT
);

2.向临时表中插入树形结构的各个节点信息。

INSERT INTO temp_tree (id, name, parent_id, level)
SELECT id, name, parent_id, level
FROM your_table
ORDER BY parent_id, id;

注意这里的查询语句需要按照父节点id和节点id进行排序,以保证树形结构的正确性。

3.通过递归查询的方式,构造树形结构的节点之间的关系,并将结果存储在一个数组或者集合中。

List<Node> nodeList = new ArrayList<>();
// 递归查询,构造节点之间的关系
public void buildTree(List<Node> nodeList, int parentId, int level) {
    for (Node node : temp_tree) {
        if (node.getParentId() == parentId) {
            node.setLevel(level);
            nodeList.add(node);
            buildTree(nodeList, node.getId(), level + 1);
        }
    }
}
// 构建树形结构
buildTree(nodeList, 0, 0);

4.根据构造出来的节点关系,生成树形结构的数据行。

public List<Map<String, Object>> generateTreeData(List<Node> nodeList) {
    List<Map<String, Object>> treeData = new ArrayList<>();
    for (Node node : nodeList) {
        Map<String, Object> data = new HashMap<>();
        data.put("id", node.getId());
        data.put("name", node.getName());
        data.put("level", node.getLevel());
        if (node.getParentId() != 0) {
            data.put("parentId", node.getParentId());
        }
        treeData.add(data);
    }
    return treeData;
}
// 生成树形结构数据行
List<Map<String, Object>> treeData = generateTreeData(nodeList);

注意这里生成的树形结构数据行可以根据实际需要进行定制,比如可以增加其他属性,比如节点状态、节点类型等等。

使用临时表的优缺点

1.使用临时表的主要优势是它能够在数据库中更加灵活地处理数据,因此适用于一些需要进行更加复杂操作的情况,如对树形结构进行排序、过滤等操作。

2.灵活性:使用临时表实现可以将数据加载到内存中,这使得在程序中对树形结构进行操作时更加灵活,尽管临时表在构建时需要一定的时间和资源,但在数据量较大时,在临时表中,数据已经存储在内存中,查询和修改速度更快,能够更好地支持大规模数据的处理。

同时,当你需要对数据进行分页处理时,使用方案一可能会比较麻烦,因为你需要对整个数据集进行递归并处理出树形结构,然后再进行分页操作。而使用临时表,你可以在数据库中将数据按照分页条件先筛选出来,然后再进行递归并处理出树形结构。


临时表的缺点是需要占用更多的存储空间,而且实现起来相对比较复杂,需要编写 SQL 查询语句。如果数据量较大或者需要对树形结构进行复杂的操作,使用临时表可能更加合适。

三、过程

目前采取的是 方案一:使用递归查询的方式并构建树形结构

在程序中进行递归构建树形结构,并且在此之前已经将数据全部取出,那么使用方案一也许会更加简单和高效。因为你无需再将数据导入到临时表中。使用临时表的主要优势是它能够在数据库中更加灵活地处理数据,因此适用于一些需要进行更加复杂操作的情况,如对树形结构进行排序、过滤等操作。

项目案例

目前是需要从一张字段具有父子关系的表中查询并将其转换成树形结构。

表结构:p_id表示父节点,p_id为0表示为根节点

前端渲染的结果

核心代码

树形结构体中的核心内容

public class ChapterTreeInfo implements Serializable {
    /**
     * 主键
     */
    @JsonSerialize(using = com.fasterxml.jackson.databind.ser.std.ToStringSerializer.class)
    private BigInteger id;
    /**
     * 父章节id
     */
    private BigInteger p_id;
    /**
     * 章节目录排序
     */
    private Integer order;
    /**
     * 章节id
     */
    private String chapter_id;
    /**
     * 章节名称
     */
    private String chapter_title;
    /**
     * 执行时间
     */
    private Integer limit_time;
    /**
     * 活动id
     */
    private String active_id;
    private List<ChapterTreeInfo> children;
  // getter and setter methods

核心方法

//1.通过课程id查询所有的章节信息
        List<ArproChapterInfo> arproChapterInfos = arproChapterInfoMapper.queryChapterInfo(courseId);
        //将需要的数据导入到准备好的ChapterTreeInfo结构中,为后序的递归做准备
        List<ChapterTreeInfo> chapterTreeInfos = dataProcessing(arproChapterInfos);
        //容器,用于表示整体的树形结构
        List<ChapterTreeInfo> chapterTreeInfoList=new ArrayList<>();
        //找出父节点,并为其添加子节点
        for (ChapterTreeInfo chapterTree:chapterTreeInfos) {
            //说明此节点是最初的父节点,为他添加子节点
            if(chapterTree.getP_id()==null||chapterTree.getP_id().intValue()==0){
                //找到该父节点下的所有子节点(以及子节点下的子节点...)
                ChapterTreeInfo chapterTreeInfo=findChildren(chapterTree,chapterTreeInfos);
                //为该父节点添加子节点
                chapterTreeInfoList.add(chapterTreeInfo);
            }
        }
        return chapterTreeInfoList;

dataProcessing方法将将查询出来的数据更换到树形结构中

/**
     * 将字典数据遍历放入chapterTreeInfos中
     *
     * @param arproChapterInfoList
     * @return
     */
    public static List<ChapterTreeInfo> dataProcessing(List<ArproChapterInfo> arproChapterInfoList) {
        //将entity转为自定义的model,有两种方式一种方式是进行for循环添加,一种是使用stream流进行添加,这里选择使用stream流中的peek操作允许你访问流中的每个元素,并且可以对每个元素进行一些操作
        List<ChapterTreeInfo> chapterTreeInfos=new ArrayList<>();
        arproChapterInfoList.stream().peek(chapterLeaf->{
            ChapterTreeInfo chapterTree =new ChapterTreeInfo();
            //获取节点的父id
            chapterTree.setP_id(chapterLeaf.getpId());
            //获取节点的id
            chapterTree.setChapter_id(chapterLeaf.getChapterId());
            //获取节点名字
            chapterTree.setChapter_title(chapterLeaf.getChapterTitle());
            //获取节点时间
            chapterTree.setLimit_time(chapterLeaf.getLimitTime());
            //获取节点内容
            chapterTree.setContents(chapterLeaf.getContents());
            //获取节点的信息
            chapterTree.setType(chapterLeaf.getType());
           //将节点信息存入到树形结构中
            chapterTreeInfos.add(chapterTree);
        }).collect(Collectors.toList());
        return chapterTreeInfos;
}

findChildren寻找根节点的子节点,进行递归

/**
     * 递归
     *
     * @param
     * @param chapterTreeModels
     * @return ChapterTreeModel
     */
    public static ChapterTreeInfo findChildren(ChapterTreeInfo chapterTreeModel, List<ChapterTreeInfo> chapterTreeModels) {
        for (ChapterTreeInfo chapterTree:chapterTreeModels) {
            //判断该节点的父节点id是否等于传递进来的节点的id(表名传进来的节点具有子节点)
            if (chapterTreeModel.getId().equals(chapterTree.getP_id())) {
                if (chapterTreeModel.getChildren()==null){
                    chapterTreeModel.setChildren(new ArrayList<>());
                }
                //递归查询当前节点是否还有子节点,直到没有子节点那就就将当前节点
                chapterTreeModel.getChildren().add(findChildren(chapterTree,chapterTreeModels));
            }
        }
        return chapterTreeModel;
     }

以上就是方案一的核心代码。

四、总结

  1. 通过这次博客总结对于查询树形结构有了更深的熟悉度和常用思路。
  2. 另外对于获取树形结构的另外一种方式,创建临时表的方式也有了一定的了解。以至于自己下一次遇到类似的问题多了一个思考的维度。
  1. 对于在程序中进行递归还是在数据库中递归,也有了这方面的了解,以及数据库实现递归的方式,如SQL 查询语句使用了 WITH RECURSIVE 关键字,实现了递归查询。

五、升华

  1. 对于解决问题的方式,不止一种,需要有意识的想到要去找多种的实现方式,进行对比选择一种复合当前的实现方式。
目录
相关文章
|
Java 关系型数据库 数据库连接
MyBatis Plus在AR道路上的探索之ActiveRecord
MyBatis Plus在AR道路上的探索之ActiveRecord
260 0
|
1月前
|
存储 供应链 前端开发
如何开发仓库管理系统中的库存管理板块 ?(附架构图+流程图+代码参考)
本文介绍仓库管理系统(WMS)中库存管理模块的开发,涵盖系统简介、库存管理功能设计、业务流程分析、开发技巧与代码示例,以及实现效果和常见问题解答,帮助企业实现高效、精准的库存管理。
|
存储 分布式计算 算法
分布式存储单主、多主和无中心架构的特征与趋势
分布式存储单主、多主和无中心架构的特征与趋势
分布式存储单主、多主和无中心架构的特征与趋势
|
4月前
|
弹性计算 负载均衡 网络协议
阿里云共享流量包是什么?共享流量包如何使用?
阿里云共享流量包是一种通用流量套餐,可自动抵扣ECS、EIP、CLB、共享带宽及IPv6网关等产品产生的公网流量费用。购买后无需额外设置,立即生效并自动抵扣对应地域的流量费用。适用于多地域流量管理,支持IPv4与IPv6流量,但需注意区域匹配,如亚太、欧美等不同区域的流量包仅能抵扣对应地域的流量。用户可通过阿里云【费用与成本】页面查询流量包使用情况。更多详情见官方说明。
|
2月前
|
移动开发 安全 搜索推荐
深度链接(Deep Link)实战指南:何时做?如何做?一篇讲透
本文探讨了 App 是否需要实现深度链接的问题,从产品复杂度、营销需求和用户旅程三个维度分析,帮助判断深度链接的最佳接入时机,并提供实操建议。
132 0
|
SQL 存储 缓存
MySQL是如何保证数据不丢失的?
文章详细阐述了InnoDB存储引擎中Buffer Pool与DML操作的关系。在执行插入、更新或删除操作时,InnoDB为了减少磁盘I/O,会在Buffer Pool中缓存数据页进行操作,随后将更新后的“脏页”刷新至磁盘。为防止服务宕机导致数据丢失,InnoDB采用了日志先行(WAL)机制,通过将DML操作记录为Redo Log并异步刷新到磁盘,结合双写机制和合理的日志刷新策略,确保数据的持久性和一致性。尽管如此,仍需合理配置参数以平衡性能与数据安全性。
343 1
MySQL是如何保证数据不丢失的?
|
存储 编解码 Unix
PyMuPDF 1.24.4 中文文档(十三)(2)
PyMuPDF 1.24.4 中文文档(十三)
411 0
|
自然语言处理 Java
ElasticSearch 实现分词全文检索 - term、terms查询
ElasticSearch 实现分词全文检索 - term、terms查询
442 0
|
11月前
|
人工智能 NoSQL 机器人
MongoDB Atlas与YoMio.AI近乎完美适配:推理更快速、查询更灵活、场景更丰富
随着MongoDB的新发布和革新,YoMio.AI的“闪电式发展”值得期待。
编写的Snort规则不报警怎么办?
以上步骤应该能帮助你找出规则不报警的原因,并进行相应的解决。
144 1