当前位置: 首页 > 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.dtcms.com/a/267184.html

相关文章:

  • Qt处理USB摄像头开发说明与QtMultimedia与V4L2融合应用
  • 【爬虫】逆向爬虫初体验之爬取音乐
  • 408第三季part2 - 计算机网络 - 物理层
  • 由coalesce(1)OOM引发的coalesce和repartition理解
  • 3dmax一键烘焙很多张贴图合并成一张贴图插件支持fbx/obj/blender多材质模型合并为一张贴图
  • OneCode自主UI设计体系:架构解析与核心实现
  • web前端面试-- MVC、MVP、MVVM 架构模式对比
  • Vue.js TDD开发深度指南:工具链配置与精细化测试策略
  • 爬虫工程师Chrome开发者工具简单介绍
  • Kafka消息积压的多维度解决方案:超越简单扩容的完整策略
  • 牛客刷题 — 【排序】[NOIP2010] 导弹拦截(排序枚举)
  • 光伏发电园区管理系统 - Three.js + Django 实现方案
  • React Hooks全面解析:从基础到高级的实用指南
  • 【论文解读】Referring Camouflaged Object Detection
  • SqueezeBERT:计算机视觉能为自然语言处理在高效神经网络方面带来哪些启示?
  • 7月5日星期六今日早报简报微语报早读
  • 在服务器上配置MQ注意的问题
  • Gartner《Stream Processing: 新一代数据处理范式》学习报告
  • Flink-状态恢复-isRestore分析
  • 使用影刀RPA实现每日消防巡检提醒
  • 常见高危端口风险分析与防护指南
  • PostgreSQL表操作
  • Python Fabric库【系统管理工具】全面讲解
  • MQTT与HTTP在物联网中的比较:为什么MQTT是更好的选择
  • Go语言的web框架--gin
  • 【解决“此扩展可能损坏”】Edge浏览器(chrome系列通杀))扩展损坏?一招保留数据快速修复
  • 编译ADI NO-OS工程
  • 【机器学习实战笔记 14】集成学习:XGBoost算法(一) 原理简介与快速应用
  • 数据可视化:图表选择与Python实战指南
  • 大数据在UI前端的应用探索:基于用户行为分析的产品优化策略