当前位置: 首页 > news >正文

后端树形结构

案例

在后端开发中,树形结构数据的查询和处理是一个常见的需求,比如部门管理、分类目录展示等场景。接下来,我们以一个部门管理系统为例,详细介绍如何实现后端的树查询功能。

案例背景

假设我们正在开发一个公司的内部管理系统,其中部门管理模块需要展示部门之间的层级关系。部门数据以树形结构存储,每个部门都有自己的上级部门(根部门的上级部门 ID 为 0),我们需要实现接口查询出扁平结构的部门列表以及树形结构的部门数据。

表结构

CREATE TABLE `department_info` (`id` int NOT NULL AUTO_INCREMENT COMMENT '部门ID',`dep_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '部门名称',`level` int NOT NULL COMMENT '层级',`parent_id` int NOT NULL COMMENT '父ID,0表示根节点',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;

在这里插入图片描述

在这个表结构中,id作为部门的唯一标识;dep_name存储部门名称;level表示部门在树形结构中的层级,方便后续对层级关系的处理;parent_id用于标识该部门的父部门,当parent_id为 0 时,表示该部门是根部门。

实体结构设计

在 Java 代码中,我们创建ParentDepartment实体类来映射数据库表中的数据,代码如下:

/*** 父子关系方案部门实体类* @TableName department_info*/
@TableName(value = "department_info")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ParentDepartment implements Serializable {// 序列化版本号private static final long serialVersionUID = 1L;/*** 部门ID*/@TableId(type = IdType.AUTO)private Integer id;/*** 部门名称*/private String depName;/*** 部门层级*/private Integer level;/*** 父部门ID(0表示根节点)*/private Integer parentId;/*** 子节点列表(非数据库字段)*/@TableField(exist = false)private List<ParentDepartment> children;/*** 是否为叶子节点(非数据库字段)*/@TableField(exist = false)private Boolean isLeaf;
}

实现思路

方法一:递归实现

递归是一种经典的处理树形结构数据的方法。基本思路是:首先从数据库中查询出所有的部门数据,然后找到所有根部门(即parent_id为 0 的部门),对于每个根部门,递归地查找它的子部门,将子部门添加到根部门的children列表中,直到所有部门都被正确添加到树形结构中。

    /*** 将扁平的部门列表转换为树形结构的部门列表。* 该方法会先找出所有根部门(即父部门ID为0的部门),* 然后递归构建每个根部门的子树。* * @param allDepartments 包含所有部门信息的扁平列表* @return 包含所有根部门及其子部门的树形结构列表*/public List<ParentDepartment> formatToTree(List<ParentDepartment> allDepartments) {// 用于存储所有根部门的列表List<ParentDepartment> rootDepartments = new ArrayList<>();// 遍历所有部门,找出父部门ID为0的根部门for (ParentDepartment department : allDepartments) {if (department.getParentId() == 0) {// 将根部门添加到根部门列表中rootDepartments.add(department);}}// 遍历所有根部门,为每个根部门构建子树for (ParentDepartment root : rootDepartments) {buildTree(root, allDepartments);}// 返回包含所有根部门及其子部门的树形结构列表return rootDepartments;}/*** 递归构建指定父部门的子树。* 该方法会遍历所有部门,找出当前父部门的所有子部门,* 并为每个子部门递归调用自身构建子树。* * @param parent 父部门对象* @param allDepartments 包含所有部门信息的扁平列表*/private void buildTree(ParentDepartment parent, List<ParentDepartment> allDepartments) {// 用于存储当前父部门的所有子部门的列表List<ParentDepartment> children = new ArrayList<>();// 遍历所有部门,找出当前父部门的子部门for (ParentDepartment department : allDepartments) {if (department.getParentId().equals(parent.getId())) {// 将子部门添加到子部门列表中children.add(department);// 递归构建子部门的子树buildTree(department, allDepartments);}}// 为父部门设置子部门列表parent.setChildren(children);}

方法二:hutool工具实现

Hutool 是一个功能丰富的 Java 工具类库,其中提供了方便的树形结构处理工具。使用 Hutool 实现树形结构查询更加简洁高效。

首先,需要在项目的pom.xml文件中引入 Hutool 依赖:

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.0</version>
</dependency>

实现树形结构查询的代码如下:

/*** 将扁平化的部门列表转换为树形结构* @param departments 扁平部门列表(需包含至少id, parentId, depName字段)* @return 树形结构列表(多个顶级节点构成森林结构)*/
public List<Tree<String>> formatToTreeSimple(List<ParentDepartment> departments) {// 1. 防御性编程:处理空输入if (CollUtil.isEmpty(departments)) {// 返回不可变空集合而非null,避免调用方NPEreturn Collections.emptyList();}// 2. 初始化树节点配置TreeNodeConfig config = new TreeNodeConfig();// 指定实体类字段与树节点属性的映射关系config.setIdKey("id");            // 节点ID对应实体类的id字段config.setParentIdKey("parentId");// 父节点ID对应实体类的parentId字段config.setChildrenKey("children");// 子节点集合的字段名称config.setNameKey("name");        // 节点显示名称对应实体类的depName字段// 3. 构建树形结构return TreeUtil.build(departments,   // 数据源集合"0",          // 根节点的父ID值(通常为0或null)config,       // 树配置(dept, tree) -> { // 自定义字段映射处理器// ---- 核心字段映射 ----// 设置节点ID(需转为String类型)tree.setId(dept.getId().toString());// 设置父节点ID(需转为String类型)tree.setParentId(dept.getParentId().toString());// 设置节点显示名称tree.setName(dept.getDepName());// ---- 扩展业务字段 ----// 添加部门层级信息tree.putExtra("level", dept.getLevel());// 添加叶子节点标记(根据children是否为空自动计算)tree.putExtra("isLeaf", dept.getIsLeaf());// 可继续添加其他业务字段...// tree.putExtra("manager", dept.getManagerName());});// 注:返回的List可能包含多个顶级节点(森林结构)// 通常业务中只有一个parentId="0"的根节点,可用get(0)获取
}

在这段代码中,我们先创建TreeNodeConfig对象,配置好 ID、父 ID、子节点列表以及名称对应的属性名。然后调用TreeUtil.build方法,传入部门数据列表、根节点 ID、配置对象以及一个函数式接口,在函数式接口中,我们将部门实体类的属性赋值给Tree对象,并可以根据业务需求添加额外的扩展字段。

在后端树查询的实现中,tree.putExtra("level", dept.getLevel());tree.putExtra("isLeaf", dept.getIsLeaf()); 这两行代码的作用是向树形结构的节点对象 tree 中添加额外的业务数据字段,也就是扩展字段 ,具体来说,它们的作用体现在以下几个方面:

  • 丰富节点信息:默认情况下,树形结构的节点可能只包含基础的标识信息(如节点 ID、父节点 ID、节点名称等)。通过添加levelisLeaf字段,可以让每个节点携带更多与业务相关的信息。比如level字段表示部门在树形结构中的层级,前端拿到数据后,就可以根据层级来设置不同的缩进样式,直观展示部门的层级关系;isLeaf字段表示该节点是否为叶子节点(即是否有子节点),这在前端进行交互操作时很有用,例如可以根据是否为叶子节点来决定是否显示展开 / 收缩按钮。
  • 方便业务处理:在实际业务中,很多操作需要依赖这些额外的信息。比如在权限管理中,可能不同层级的部门有不同的权限;在数据统计时,可能需要区分叶子节点和非叶子节点进行不同的计算。将这些字段直接附加到树形结构的节点中,在后续业务逻辑处理时,就无需再通过复杂的查询或计算来获取,提高开发效率。
  • 增强数据通用性:添加扩展字段使树形结构数据更具通用性和灵活性。即使当前业务不需要这些字段,未来如果有新的功能需求,比如添加部门层级相关的筛选功能,或者根据叶子节点状态进行特殊展示等,已经存在的扩展字段就能直接使用,而不需要对数据结构和代码进行大规模修改。
http://www.lryc.cn/news/580360.html

相关文章:

  • STM32F103RCTx的PWM输出控制电机
  • js游戏简单修改
  • React Native 开发环境搭建--mac--android--奔溃的一天
  • Hinge×亚矩云手机:以“深度连接”为名,重构云端社交的“真实感”
  • CSS02:四种CSS导入方式
  • pyspark大规模数据加解密优化实践
  • Python小工具之PDF合并
  • 数据结构:多维数组在内存中的映射(Address Mapping of Multi-dimensional Arrays)
  • IDEA中application.yml配置文件不自动提示解决办法
  • 如何在IntelliJ IDEA中设置数据库连接全局共享
  • 从“电话催维修“到“手机看进度“——售后服务系统开发如何重构客户体验
  • CppCon 2018 学习:Surprises In Object Lifetime
  • Kotlin 协程:Channel 与 Flow 深度对比及 Channel 使用指南
  • 【ES6】Latex总结笔记生成器(网页版)
  • Jenkins Pipeline(二)
  • 【Elasticsearch】深度分页及其替代方案
  • 【openp2p】 学习2:源码阅读P2PNetwork和P2PTunnel
  • 【STM32实践篇】:GPIO 详解
  • 网络资源模板--基于Android Studio 实现的极简天气App
  • Excel 数据透视表不够用时,如何处理来自多个数据源的数据?
  • 动手实践OpenHands系列学习笔记1:Docker基础与OpenHands容器结构
  • Softhub软件下载站实战开发(十三):软件管理前端分片上传实现
  • 用户中心Vue3网页开发(1.0版)
  • Java零基础笔记01(JKD及开发工具IDEA安装配置)
  • Linux进程管理:从基础到实战
  • 60天python训练计划----day59
  • 数据结构:数组:插入操作(Insert)与删除操作(Delete)
  • 深度学习4(浅层神经网络)
  • 【深度学习】神经网络剪枝方法的分类
  • 由coalesce(1)OOM引发的coalesce和repartition理解