封装一个工具类,拒绝重复代码!

一、介绍

为了简化开发过程并提高代码的可维护性,我们可以创建一个统一的工具类来处理这些需求。

在本文中,我将介绍如何使用SpringBoot创建一个返回多级菜单、多级评论、多级部门、多级分类的统一工具类。

介绍数据库字段设计

数据库设计

「主要是介绍是否需要tree_path字段。」

多级节点的数据库大家都知道,一般会有id,parentId字段,但是对于tree_path字段,这个需要根据设计者来定。

优点:

  • 如果你对数据的读取操作比较频繁,而且需要快速查询某个节点的所有子节点或父节点,那么使用tree_path 字段可以提高查询效率。

  • tree_path 字段可以使用路径字符串表示节点的层级关系,例如使用逗号分隔的节点ID列表。这样,可以通过模糊匹配tree_path 字段来查询某个节点的所有子节点或父节点,而无需进行递归查询。

  • 你可以使用模糊匹配的方式,找到所有以该节点的 tree_path 开头的子节点,并将它们删除。而无需进行递归删除。

缺点:

  • 每次插入时,需要更新tree_path 字段,这可能会导致性能下降。

  • tree_path 字段的长度可能会随着树的深度增加而增加,可能会占用更多的存储空间。

因此,在设计数据库评论字段时,需要权衡使用treepath字段和父评论ID字段的优缺点,并根据具体的应用场景和需求做出选择。如果你更关注读取操作的效率和查询、删除的灵活性,可以考虑使用tree_path 字段。如果你更关注写入操作的效率和数据一致性,并且树的深度不会很大,那么使用父评论ID字段来实现多级评论可能更简单和高效。

二、统一工具类具体实现

1. 定义接口,统一规范

对于有 lombok 的小伙伴,实现这个方法很简单,只需要加上@Data即可

/*** @Description: 固定属性结构属性* @Author: yiFei*/
public interface ITreeNode<T> {/*** @return 获取当前元素Id*/Object getId();/*** @return 获取父元素Id*/Object getParentId();/*** @return 获取当前元素的 children 属性*/List<T> getChildren();/*** ( 如果数据库设计有tree_path字段可覆盖此方法来生成tree_path路径 )** @return 获取树路径*/default Object getTreePath() { return ""; }
}

2. 编写工具类TreeNodeUtil

其中我们需要实现能将一个List元素构建成熟悉结构

我们需要实现生成tree_path字段

我们需要优雅的实现该方法

/*** @Description: 树形结构工具类* @Author: yiFei*/
public class TreeNodeUtil {private static final Logger log = LoggerFactory.getLogger(TreeNodeUtil.class);public static final String PARENT_NAME = "parent";public static final String CHILDREN_NAME = "children";public static final List<Object> IDS = Collections.singletonList(0L);public static <T extends ITreeNode> List<T> buildTree(List<T> dataList) {return buildTree(dataList, IDS, (data) -> data, (item) -> true);}public static <T extends ITreeNode> List<T> buildTree(List<T> dataList, Function<T, T> map) {return buildTree(dataList, IDS, map, (item) -> true);}public static <T extends ITreeNode> List<T> buildTree(List<T> dataList, Function<T, T> map, Predicate<T> filter) {return buildTree(dataList, IDS, map, filter);}public static <T extends ITreeNode> List<T> buildTree(List<T> dataList, List<Object> ids) {return buildTree(dataList, ids, (data) -> data, (item) -> true);}public static <T extends ITreeNode> List<T> buildTree(List<T> dataList, List<Object> ids, Function<T, T> map) {return buildTree(dataList, ids, map, (item) -> true);}/*** 数据集合构建成树形结构 ( 注: 如果最开始的 ids 不在 dataList 中,不会进行任何处理 )** @param dataList 数据集合* @param ids      父元素的 Id 集合* @param map      调用者提供 Function<T, T> 由调用着决定数据最终呈现形势* @param filter   调用者提供 Predicate<T> false 表示过滤 ( 注: 如果将父元素过滤掉等于剪枝 )* @param <T>      extends ITreeNode* @return*/public static <T extends ITreeNode> List<T> buildTree(List<T> dataList, List<Object> ids, Function<T, T> map, Predicate<T> filter) {if (CollectionUtils.isEmpty(ids)) {return Collections.emptyList();}// 1. 将数据分为 父子结构Map<String, List<T>> nodeMap = dataList.stream().filter(filter).collect(Collectors.groupingBy(item -> ids.contains(item.getParentId()) ? PARENT_NAME : CHILDREN_NAME));List<T> parent = nodeMap.getOrDefault(PARENT_NAME, Collections.emptyList());List<T> children = nodeMap.getOrDefault(CHILDREN_NAME, Collections.emptyList());// 1.1 如果未分出或过滤了父元素则将子元素返回if (parent.size() == 0) {return children;}// 2. 使用有序集合存储下一次变量的 idsList<Object> nextIds = new ArrayList<>(dataList.size());// 3. 遍历父元素 以及修改父元素内容List<T> collectParent = parent.stream().map(map).collect(Collectors.toList());for (T parentItem : collectParent) {// 3.1 如果子元素已经加完,直接进入下一轮循环if (nextIds.size() == children.size()) {break;}// 3.2 过滤出 parent.id == children.parentId 的元素children.stream().filter(childrenItem -> parentItem.getId().equals(childrenItem.getParentId())).forEach(childrenItem -> {// 3.3 这次的子元素为下一次的父元素nextIds.add(childrenItem.getParentId());// 3.4 添加子元素到 parentItem.children 中try {parentItem.getChildren().add(childrenItem);} catch (Exception e) {log.warn("TreeNodeUtil 发生错误, 传入参数中 children 不能为 null,解决方法: \n" +"方法一、在map(推荐)或filter中初始化 \n" +"方法二、List<T> children = new ArrayList<>() \n" +"方法三、初始化块对属性赋初值\n" +"方法四、构造时对属性赋初值");}});}buildTree(children, nextIds, map, filter);return parent;}/*** 生成路径 treePath 路径** @param currentId 当前元素的 id* @param getById   用户返回一个 T* @param <T>* @return*/public static <T extends ITreeNode> String generateTreePath(Serializable currentId, Function<Serializable, T> getById) {StringBuffer treePath = new StringBuffer();if (SystemConstants.ROOT_NODE_ID.equals(currentId)) {// 1. 如果当前节点是父节点直接返回treePath.append(currentId);} else {// 2. 调用者将当前元素的父元素查出来,方便后续拼接T byId = getById.apply(currentId);// 3. 父元素的 treePath + "," + 父元素的idif (!ObjectUtils.isEmpty(byId)) {treePath.append(byId.getTreePath()).append(",").append(byId.getId());}}return treePath.toString();}}

这样我们就完成了 TreeNodeUtil 统一工具类,首先我们将元素分为父子两类,让其构建出一个小型树,然后我们将构建的子元素和下次遍历的父节点传入,递归的不断进行,这样就构建出了我们最终的想要实现的效果。

三、测试

定义一个类实现 ITreeNode

/*** @Description: 测试子元素工具类* @Author: yiFei*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@AllArgsConstructor
public class TestChildren implements ITreeNode<TestChildren> {private Long id;private String name;private String treePath;private Long parentId;public TestChildren(Long id, String name, String treePath, Long parentId) {this.id = id;this.name = name;this.treePath = treePath;this.parentId = parentId;}@TableField(exist = false)private List<TestChildren> children = new ArrayList<>();
}

测试基本功能

测试基本功能代码:

public static void main(String[] args) {List<TestChildren> testChildren = new ArrayList<>();testChildren.add(new TestChildren(1L, "父元素", "", 0L));testChildren.add(new TestChildren(2L, "子元素1", "1", 1L));testChildren.add(new TestChildren(3L, "子元素2", "1", 1L));testChildren.add(new TestChildren(4L, "子元素2的孙子元素", "1,3", 3L));testChildren = TreeNodeUtil.buildTree(testChildren);System.out.println(JSONUtil.toJsonStr(Result.success(testChildren)));
}

返回结果:

{"code": "00000","msg": "操作成功","data": [{"id": 1,"name": "父元素","treePath": "","parentId": 0,"children": [{"id": 2,"name": "子元素1","treePath": "1","parentId": 1,"children": []}, {"id": 3,"name": "子元素2","treePath": "1","parentId": 1,"children": [{"id": 4,"name": "子元素2的孙子元素","treePath": "1,3","parentId": 3,"children": []}]}]}]
}  

测试过滤以及重构数据

测试代码:

public static void main(String[] args) {List<TestChildren> testChildren = new ArrayList<>();testChildren.add(new TestChildren(1L, "父元素", "", 0L));testChildren.add(new TestChildren(2L, "子元素1", "1", 1L));testChildren.add(new TestChildren(3L, "子元素2", "1", 1L));testChildren.add(new TestChildren(4L, "子元素2的孙子元素", "1,3", 3L));testChildren = TreeNodeUtil.buildTree(testChildren);System.out.println(JSONUtil.toJsonStr(Result.success(testChildren)));
}

返回结果 :

{"code": "00000","msg": "操作成功","data": [{"id": 1,"name": "父元素","treePath": "","parentId": 0,"children": [{"id": 2,"name": "子元素1","treePath": "1","parentId": 1,"children": []}, {"id": 3,"name": "子元素2","treePath": "1","parentId": 1,"children": [{"id": 4,"name": "子元素2的孙子元素","treePath": "1,3","parentId": 3,"children": []}]}]}]
}  

测试过滤以及重构数据

测试代码:

// 对 3L 进行剪枝,对 1L 进行修改
testChildren = TreeNodeUtil.buildTree(testChildren, (item) -> {if (item.getId().equals(1L)) {item.setName("更改了 Id 为 1L 的数据名称");}return item;
}, (item) -> item.getId().equals(3L));

返回结果:

{"code": "00000","msg": "操作成功","data": [{"id": 1,"name": "更改了 Id 为 1L 的数据名称","treePath": "","parentId": 0,"children": [{"id": 2,"name": "子元素1","treePath": "1","parentId": 1,"children": []}]}]
}

接下来的测试结果以口述的方式讲解

测试传入错误的 ids

  • 返回传入的 testChildren

测试传入具有父子结构,但是 ids 传错的情况 (可以根据实际需求更改是否自动识别父元素)

  • 返回传入的 testChildren

测试  testChildren 中 children元素为 null

  • 给出提示,不构建树

测试 generateTreePath 生成路径

  • 返回路径

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/29687.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在C语言中,可以使用strtol函数来将字符串中的16进制数转换为10进制整数

在C语言中&#xff0c;可以使用strtol函数来将字符串中的16进制数转换为10进制整数&#xff0c;进而转换为MAC地址的字节表示。以下是一个将字符串形式的MAC地址转换为16进制表示的例子&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.…

npm install cnpm -g 报错4048

npm install cnpm -g 报错4048 设置淘宝镜像&#xff1a; 报错如下&#xff1a; 其他博主提供的方法都尝试了&#xff0c;比如管理员权限打开终端&#xff0c;删除.npmrc文件&#xff0c;清除缓存npm cache clean -f等都试了无效&#xff0c;最后怀疑是npm和cnpm版本不对应&…

数据库面试题-Redis

数据库面试题-Redis 1、Redis是什么?2、为什么要使用Redis?3、说说Redis和Memcache的异同?4、说说Redis的线程模型?5、为什么Redis是单线程模型效率也能那么高?6、为什么Redis需要把所有数据放到内存中?7、说说Redis 的同步机制?8、Redis中pipeline有什么好处,为什么要…

Springboot全局异常处理和统一返回结果

①统一返回结果 package com.ioc.exception;import lombok.Data;import java.io.Serializable;/*** 后端统一返回结果* param <T>*/ Data //使用泛型对Result返回类进行了封装 public class Result<T> implements Serializable {private Integer code; //编码&…

JSP之原理剖析

什么是JSP&#xff1a; java Server Pages: java服务端页面,也和Servlet一样&#xff0c;用于动态Web技术&#xff1f; 最大特点&#xff1a; 写jsp就像在写HTML区别&#xff1a; HTML只给用户提供静态的数据JSP页面中可以嵌入Java代码&#xff0c;为用户提供动态数据 JSP原…

基于STM32和人工智能的智能四轴飞行器系统

目录 引言环境准备智能四轴飞行器系统基础代码实现&#xff1a;实现智能四轴飞行器系统 4.1 数据采集模块4.2 数据处理与分析4.3 控制系统4.4 用户界面与数据可视化应用场景&#xff1a;智能飞行器管理与优化问题解决方案与优化收尾与总结 1. 引言 随着无人机技术的发展&…

群晖NAS部署在线PS工具Potopea并实现浏览器远程访问处理图片

文章目录 前言1. 部署Photopea2. 运行Photopea3. 群晖安装Cpolar4. 配置公网地址5. 公网访问测试6. 固定公网地址 前言 本文主要介绍如何在群晖NAS本地部署Potopea在线图片PS编辑工具&#xff0c;并结合cpolar内网穿透实现公网环境远程访问本地部署的Potopea处理图片. Photop…

【INTEL(ALTERA)】make: nios2-swexample-create:未找到命令

目录 说明 解决方法 说明 由于外部内存接口英特尔 Stratix 10 FPGA IP 出现问题&#xff0c;如果在 Windows 平台上使用英特尔 Quartus Prime Pro Edition Software v20.4 或更早版本的"使用软Nios处理器进行片上调试"选项&#xff0c;编译Nios II 片上处理器调试…

2288. 价格减免 Medium

句子 是由若干个单词组成的字符串&#xff0c;单词之间用单个空格分隔&#xff0c;其中每个单词可以包含数字、小写字母、和美元符号 $ 。如果单词的形式为美元符号后跟着一个非负实数&#xff0c;那么这个单词就表示一个 价格 。 例如 "$100"、"$23" 和 &…

【CT】LeetCode手撕—103. 二叉树的锯齿形层序遍历

目录 题目1- 思路2- 实现⭐103. 二叉树的锯齿形层序遍历——题解思路 2- ACM实现 题目 原题连接&#xff1a;103. 二叉树的锯齿形层序遍历 1- 思路 二叉树的层序遍历&#xff0c;遇到奇数时&#xff0c;利用 Collections.reverse() 翻转即可 2- 实现 ⭐103. 二叉树的锯齿形层…

USMT(微软用户状态迁移工具) 入门指南

前言 以下知识来自,我实际经验总结,官网文档不会这样讲. 如有遗漏或错误,请指正. 如有新的想法,请评论,我将添加到此文,并以您的昵称为来源标注在文中. 自定义XML语法 基础 语法: path\ [filename]例如: %SYSTEMROOT%\MyVideo\ [邓文怡和老师.mp4]这会迁移或排除C:\\MyVi…

【第17章】Vue实战篇之注册界面

文章目录 前言一、搭建界面二、数据绑定1.定义响应数据2.绑定数据 三、数据校验1. 定义校验规则2. 表单校验3. 展示 四、注册接口调用1. user.js2. Login.vue 五、展示总结 前言 本章内容主要包含&#xff1a; 注册界面搭建数据绑定表单校验调用后端接口 一、搭建界面 <s…

Java23种设计模式(二)

1、单例模式 单例模式&#xff08;Singleton Pattern&#xff09;是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类&#xff0c;该类负责创建自己的对象&#xff0c;同时确保只有…

电脑硬盘文件隐藏了怎么恢复数据?轻松上手不求人

在数字化时代&#xff0c;电脑硬盘中存储的数据对于个人和企业而言都至关重要。然而&#xff0c;有时我们可能会遇到一些意想不到的情况&#xff0c;比如硬盘中的文件突然消失了或者变成了隐藏状态。这种情况不仅让人感到困惑&#xff0c;还可能对日常工作和生活造成不小的影响…

【unity笔记】三、冰山碰撞变成碎块效果

一、模型准备 共需准备两个模型&#xff0c;一个原始模型&#xff0c;一个破碎后的模型。 破碎后的模型制作教程&#xff1a; 下载Blender 导入原始模型在添加偏好设置中添加Cell Fracture插件&#xff0c;调整模型碎裂效果。导出&#xff0c;保存到项目预制体文件夹。 二、…

胡说八道(24.6.17)——STM32以及通信杂谈

之前的文章中咱们谈到了STM32的时钟&#xff0c;今天我们来联系实际&#xff0c;来看看内部时钟下和外部时钟下的两种不同时钟的电平翻转。本次终于有硬件了&#xff0c;是最基础的STM32F103C8T6。 首先是&#xff0c;内部时钟的配置操作。 系统的内部时钟是72MHz&#xff0c;由…

DeepSeek V2 可以直接生成2048游戏代码,并可以运行

在 macOS 上&#xff0c;你可以使用 curses 库来实现 2048 游戏&#xff0c;就像在 Windows 上一样。curses 库在 macOS 上是内置的&#xff0c;所以你不需要额外安装任何库。 你可以直接使用我之前提供的代码。以下是完整的代码&#xff0c;你可以将其保存为一个 Python 文件…

【Windows系统】文件操作出现“文件访问被拒绝”弹窗问题

环境 系统&#xff1a;win10x64 版本&#xff1a;1709 问题 重命名系统文件夹文件&#xff0c;有时会出现【文件访问被拒绝】的弹窗&#xff0c;导致操作失败。 如何才能避免弹窗&#xff0c;成功操作&#xff1f; 解决方法 前提&#xff1a;实施以下解决&#xff0c;首先…

JavaScript Prototype

JavaScript Prototype JavaScript 是一种高级的、解释执行的编程语言&#xff0c;广泛应用于网页和服务器端开发。JavaScript 的核心特性之一是其原型继承机制&#xff0c;这是理解 JavaScript 对象模型的关键。 什么是 Prototype&#xff1f; 在 JavaScript 中&#xff0c;…

设置角色运动的动画

(1) 打开Assets-UnityTechnologies-Animation-Animators&#xff0c;Create-Animation-Controller,命名为JohnLemon (2) 打开JohnLemon&#xff0c;出现下图 (3) 依次将Assets-UnityTechnologies-Animation-Animation中的JohnIdle和JohnWalk拖放到Base Layer窗口中 (4) 右击Idl…