使用SpringBoot实现无限级评论回复功能
评论功能已经成为APP和网站开发中的必备功能。本文采用springboot+mybatis-plus框架,通过代码主要介绍评论功能的数据库设计和接口数据返回。我们返回的格式可以分三种方案,第一种方案是先返回评论,再根据评论id返回回复信息,第二种方案是将评论回复直接封装成一个类似于树的数据结构进行返回(如果数据对的话,可以根据评论分页),第三种方案是将所有数据用递归的SQL查出来,再把数据解析成树,返回结果
1 数据库表结构设计
表结构:
CREATE TABLE `comment` (`id` bigint(18) NOT NULL AUTO_INCREMENT,`parent_id` bigint(18) NOT NULL DEFAULT '0',`content` text NOT NULL COMMENT '内容',`author` varchar(20) NOT NULL COMMENT '评论人',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '评论时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
数据添加:
INSERT INTO `comment` (`id`, `parent_id`, `content`, `author`, `create_time`) VALUES (1, 0, '这是评论1', '吴名氏', '2023-02-20 17:11:16');
INSERT INTO `comment` (`id`, `parent_id`, `content`, `author`, `create_time`) VALUES (2, 1, '我回复了第一条评论', '吴名氏', '2023-02-20 17:12:00');
INSERT INTO `comment` (`id`, `parent_id`, `content`, `author`, `create_time`) VALUES (3, 2, '我回复了第一条评论的第一条回复', '吴名氏', '2023-02-20 17:12:13');
INSERT INTO `comment` (`id`, `parent_id`, `content`, `author`, `create_time`) VALUES (4, 2, '我回复了第一条评论的第二条回复', '吴名氏', '2023-02-21 09:23:14');
INSERT INTO `comment` (`id`, `parent_id`, `content`, `author`, `create_time`) VALUES (5, 0, '这是评论2', '吴名氏', '2023-02-21 09:41:02');
INSERT INTO `comment` (`id`, `parent_id`, `content`, `author`, `create_time`) VALUES (6, 3, '我回复了第一条评论的第一条回复的第一条回复', '吴名氏', '2023-02-21 09:56:27');
添加后的数据:

2 方案一
方案一先返回评论列表,再根据评论id返回回复列表,以此循环,具体代码下文进行展示
2.1 控制层CommentOneController.java
/*** 方案一* @author wuKeFan* @date 2023-02-20 16:58:08*/
@Slf4j
@RestController
@RequestMapping("/one/comment")
public class CommentOneController {@Resourceprivate CommentService commentService;@GetMapping("/")public List<Comment> getList() {return commentService.getList();}@GetMapping("/{id}")public Comment getCommentById(@PathVariable Long id) {return commentService.getById(id);}@GetMapping("/parent/{parentId}")public List<Comment> getCommentByParentId(@PathVariable Long parentId) {return commentService.getCommentByParentId(parentId);}@PostMapping("/")public void addComment(@RequestBody Comment comment) {commentService.addComment(comment);}}
2.2 service类CommentService.java
/*** service类* @author wuKeFan* @date 2023-02-20 16:55:23*/
public interface CommentService {List<Comment> getCommentByParentId(Long parentId);void addComment(Comment comment);Comment getById(Long id);List<Comment> getList();
}
2.3 service实现类CommentServiceImpl.java
/*** @author wuKeFan* @date 2023-02-20 16:56:00*/
@Service
public class CommentServiceImpl implements CommentService{@Resourceprivate CommentMapper baseMapper;@Overridepublic List<Comment> getCommentByParentId(Long parentId) {QueryWrapper<Comment> queryWrapper = new QueryWrapper<>();queryWrapper.eq("parent_id", parentId);return baseMapper.selectList(queryWrapper);}@Overridepublic void addComment(Comment comment) {baseMapper.insert(comment);}@Overridepublic Comment getById(Long id) {return baseMapper.selectById(id);}@Overridepublic List<Comment> getList() {return baseMapper.selectList(new QueryWrapper<Comment>().lambda().eq(Comment::getParentId, 0));}}
2.4 数据库持久层类CommentMapper.java
/*** mapper类* @author wuKeFan* @date 2023-02-20 16:53:59*/
@Repository
public interface CommentMapper extends BaseMapper<Comment> {}
2.5 实体类Comment.java
/*** 评论表实体类* @author wuKeFan* @date 2023-02-20 16:53:24*/
@Data
@TableName("comment")
public class Comment {@TableId(type = IdType.AUTO)private Long id;private Long parentId;private String content;private String author;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date createTime;
}
2.6 使用Postman请求接口,查看返回数据
2.6.1 请求评论列表接口(本地的url为:http://localhost:8081/one/comment/ ,GET请求),请求结果如图

2.6.2 根据评论id(或者回复id)返回回复列表(本地url为:http://localhost:8081/one/comment/parent/1 ,GET请求),请求结果如图

3 方案二
方案二采用的是将数据装到一个类似树的数据结构,然后返回,数据如果多的话,可以根据评论列表进行分页
3.1 控制层CommentTwoController.java
/*** @author wuKeFan* @date 2023-02-20 17:30:45*/
@Slf4j
@RestController
@RequestMapping("/two/comment")
public class CommentTwoController {@Resourceprivate CommentService commentService;@GetMapping("/")public List<CommentDTO> getAllComments() {return commentService.getAllComments();}@PostMapping("/")public void addComment(@RequestBody Comment comment) {commentService.addComment(comment);}
}
3.2 service类CommentService.java
/*** service类* @author wuKeFan* @date 2023-02-20 16:55:23*/
public interface CommentService {void addComment(Comment comment);void setChildren(CommentDTO commentDTO);List<CommentDTO> getAllComments();
}
3.3 service实现类CommentServiceImpl.java
/*** @author wuKeFan* @date 2023-02-20 16:56:00*/
@Service
public class CommentServiceImpl implements CommentService{@Resourceprivate CommentMapper baseMapper;@Overridepublic void addComment(Comment comment) {baseMapper.insert(comment);}@Overridepublic List<CommentDTO> getAllComments() {List<CommentDTO> rootComments = baseMapper.findByParentId(0L);rootComments.forEach(this::setChildren);return rootComments;}/*** 递归获取* @param commentDTO 参数*/@Overridepublic void setChildren(CommentDTO commentDTO){List<CommentDTO> children = baseMapper.findByParentId(commentDTO.getId());if (!children.isEmpty()) {commentDTO.setChildren(children);children.forEach(this::setChildren);}}}
3.4 数据库持久层类CommentMapper.java
/*** mapper类* @author wuKeFan* @date 2023-02-20 16:53:59*/
@Repository
public interface CommentMapper extends BaseMapper<Comment> {@Select("SELECT id, parent_id as parentId, content, author, create_time as createTime FROM comment WHERE parent_id = #{parentId}")List<CommentDTO> findByParentId(Long parentId);}
3.5 实体类CommentDTO.java
/*** 递归方式实体类* @author wuKeFan* @date 2023-02-20 17:26:48*/
@Data
public class CommentDTO {private Long id;private Long parentId;private String content;private String author;private List<CommentDTO> children;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date createTime;}
3.6 使用Postman请求接口,查看返回数据
3.6.1 通过递归的方式以树的数据结构返回(本地url为:http://localhost:8081/two/comment/ ,GET请求),请求结果如图

返回的json格式如图:
[{"id": 1,"parentId": 0,"content": "这是评论1","author": "吴名氏","children": [{"id": 2,"parentId": 1,"content": "我回复了第一条评论","author": "吴名氏","children": [{"id": 3,"parentId": 2,"content": "我回复了第一条评论的第一条回复","author": "吴名氏","children": [{"id": 6,"parentId": 3,"content": "我回复了第一条评论的第一条回复的第一条回复","author": "吴名氏","children": null,"createTime": "2023-02-21 09:56:27"}],"createTime": "2023-02-20 17:12:13"},{"id": 4,"parentId": 2,"content": "我回复了第一条评论的第二条回复","author": "吴名氏","children": null,"createTime": "2023-02-21 09:23:14"}],"createTime": "2023-02-20 17:12:00"}],"createTime": "2023-02-20 17:11:16"},{"id": 5,"parentId": 0,"content": "这是评论2","author": "吴名氏","children": null,"createTime": "2023-02-21 09:41:02"}
]
4 方案三
方案三是将所有数据用递归的SQL查出来,再把数据解析成树,返回结果,适合数据较少的情况下,且MySQL版本需要在8.0以上
4.1 控制层CommentThreeController.java
/*** @author wuKeFan* @date 2023-02-20 17:30:45*/
@Slf4j
@RestController
@RequestMapping("/three/comment")
public class CommentThreeController {@Resourceprivate CommentService commentService;@GetMapping("/")public List<CommentDTO> getAllCommentsBySql() {return commentService.getAllCommentsBySql();}@PostMapping("/")public void addComment(@RequestBody Comment comment) {commentService.addComment(comment);}
}
4.2 service类CommentService.java
/*** service类* @author wuKeFan* @date 2023-02-20 16:55:23*/
public interface CommentService {List<CommentDTO> getAllCommentsBySql();}
4.3 service实现类CommentServiceImpl.java
/*** @author wuKeFan* @date 2023-02-20 16:56:00*/
@Service
public class CommentServiceImpl implements CommentService{@Resourceprivate CommentMapper baseMapper;/*** 递归获取* @param commentDTO 参数*/@Overridepublic void setChildren(CommentDTO commentDTO){List<CommentDTO> children = baseMapper.findByParentId(commentDTO.getId());if (!children.isEmpty()) {commentDTO.setChildren(children);children.forEach(this::setChildren);}}}
4.4 数据库持久层类CommentMapper.java
/*** mapper类* @author wuKeFan* @date 2023-02-20 16:53:59*/
@Repository
public interface CommentMapper extends BaseMapper<Comment> {@DS("localhost80")List<CommentDTO> getAllCommentsBySql();}
4.5 实体类Comment.java
/*** 评论表实体类* @author wuKeFan* @date 2023-02-20 16:53:24*/
@Data
@TableName("comment")
public class Comment {@TableId(type = IdType.AUTO)private Long id;private Long parentId;private String content;private String author;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date createTime;private Integer level;
}
4.6 使用Postman请求接口,查看返回数据
4.6.1 通过递归的方式以树的数据结构返回(本地url为:http://localhost:8081/three/comment/ ,GET请求),请求结果如图

返回的json格式如图:
[{"id": 1,"parentId": 0,"content": "这是评论1","author": "吴名氏","children": [{"id": 2,"parentId": 1,"content": "我回复了第一条评论","author": "吴名氏","children": [{"id": 3,"parentId": 2,"content": "我回复了第一条评论的第一条回复","author": "吴名氏","children": [{"id": 6,"parentId": 3,"content": "我回复了第一条评论的第一条回复的第一条回复","author": "吴名氏","children": [],"createTime": "2023-02-21 09:56:27","level": 4}],"createTime": "2023-02-20 17:12:13","level": 3},{"id": 4,"parentId": 2,"content": "我回复了第一条评论的第二条回复","author": "吴名氏","children": [],"createTime": "2023-02-21 09:23:14","level": 3}],"createTime": "2023-02-20 17:12:00","level": 2}],"createTime": "2023-02-20 17:11:16","level": 1},{"id": 5,"parentId": 0,"content": "这是评论2","author": "吴名氏","children": [],"createTime": "2023-02-21 09:41:02","level": 1}
]
5 总结
以上三种方案各有优缺点,需要从不同场景中使用不同的方案
相关文章:

使用SpringBoot实现无限级评论回复功能
评论功能已经成为APP和网站开发中的必备功能。本文采用springbootmybatis-plus框架,通过代码主要介绍评论功能的数据库设计和接口数据返回。我们返回的格式可以分三种方案,第一种方案是先返回评论,再根据评论id返回回复信息,第二种方案是将评论回复直接封装成一个类似于树的数据…...

Kafka 介绍和使用
文章目录前言1、Kafka 系统架构1.1、Producer 生产者1.2、Consumer 消费者1.3、Consumer Group 消费者群组1.4、Topic 主题1.5、Partition 分区1.6、Log 日志存储1.7、Broker 服务器1.8、Offset 偏移量1.9、Replication 副本1.10、Zookeeper2、Kafka 环境搭建2.1、下载 Kafka2.…...

[学习笔记]Rocket.Chat业务数据备份
Rocket.Chat 的业务数据主要存储于mongodb数据库的rocketchat库中,聊天中通过发送文件功能产生的文件储存于/app/uploads中(文件方式设置为"FileSystem"),因此在对Rocket.Chat做数据移动或备份主要分为两步,…...
【ZOJ 1090】The Circumference of the Circle 题解(海伦公式+正弦定理推论)
计算圆的周长似乎是一项简单的任务——只要你知道它的直径。但如果你没有呢? 我们给出了平面中三个非共线点的笛卡尔坐标。 您的工作是计算与所有三个点相交的唯一圆的周长。 输入规范 输入文件将包含一个或多个测试用例。每个测试用例由一条包含六个实数x1、y1、x…...
【go】slice原理
slice包含3个部分: 1.内存的起始位置 2.切片的大小(已经存放的元素数量) 3.容量(可以存放的元素数量) 使用make初始化切片会开辟底层内存,并初始化元素值为默认值,如数字为0,字符串为空 使用New初始化切片不会开辟底层数组&…...
【数据库】MySQL概念知识语法-基础篇(DQL),真的很详细,一篇文章你就会了
目录通用语法及分类DQL(数据查询语言)基础查询条件查询聚合查询(聚合函数)分组查询排序查询分页查询内连接查询外连接查询自连接查询联合查询子查询列子查询行子查询表子查询总结通用语法及分类 ● DDL: 数据定义语言,…...

博客界的至高神:属于自己的WordPress网站,你值得拥有!
【如果暂时没时间安装,可以直接跳转到最后先看展示效果】 很多朋友都想有一个对外展示的窗口,在那里放一些个人的作品或者其他想对外分享的东西。大部分人选择了在微博、公众号等平台,毕竟这些平台流量大,我们可以很轻易地把自己…...

操作系统(day13)-- 虚拟内存;页面分配策略
虚拟内存管理 虚拟内存的基本概念 传统存储管理方式的特征、缺点 一次性: 作业必须一次性全部装入内存后才能开始运行。驻留性:作业一旦被装入内存,就会一直驻留在内存中,直至作业运行结束。事实上,在一个时间段内&…...

SQL零基础入门学习(四)
SQL零基础入门学习(三) SQL INSERT INTO 语句 INSERT INTO 语句用于向表中插入新记录。 SQL INSERT INTO 语法 INSERT INTO 语句可以有两种编写形式。 第一种形式无需指定要插入数据的列名,只需提供被插入的值即可: INSERT …...

19岁就患老年痴呆!这些前兆别忽视!
在大部分人的印象中,阿尔兹海默症好像是专属于老年人的疾病,而且它的另一个名字就是老年痴呆症。然而,前不久,一位19岁的男生患上了阿尔兹海默症,是迄今为止最年轻的患者。这个男生从17岁开始,就出现了注意…...

【C++】thread|mutex|atomic|condition_variable
本篇博客,让我们来认识一下C中的线程操作 所用编译器:vs2019 阅读本文前,建议先了解线程的概念 👉 线程概念 1.基本介绍 在不同的操作系统,windows、linux、mac上,都会对多线程操作提供自己的系统调用接口…...

学成在线项目笔记
业务层开发 DAO开发示例 生成实体类对应的mapper和xml文件 定义MybatisPlusConfig,用于扫描mapper和配置分页拦截器 MapperScan("com.xuecheng.content.mapper") Configuration public class MybatisPlusConfig {Beanpublic MybatisPlusInterceptor myb…...

FreeRTOS队列
队列简介队列是一种任务到任务,任务到中断,中断到任务数据交流得一种机制。在队列中可以存储数量有限,大小固定得多个数据,队列中的每一个数据叫做队列项目,队列能够存储队列项目的最大数量称为队列的长度,…...

rancher2安装nfs-subdir-external-provisioner为PVC/PV动态提供存储空间(动态分配卷)
接上一篇《centos7部署rancher2.5详细图文教程》 一、 安装nfs服务 1. 所有节点都需要操作 $ # 下载 nfs 相关软件 $ sudo yum -y install nfs-utils rpcbind$ # 启动服务并加入开机自启 $ sudo systemctl start nfs && systemctl enable nfs $ sudo systemctl star…...

1.JAVA-JDK安装
前言:工具下载地址阿里云盘:Java-Jdk:https://www.aliyundrive.com/s/JpV55xhVq2A提取码: j53y一、jdk下载:前往Oracle官网可免费下载地址:https://www.oracle.com/java/technologies/downloads/ 此处我下载的是jdk8&a…...
Java必备小知识点4——数据类型、数组、位运算符
数据类型Java的数据类型由基本数据类型和引用类型基本数据类型和C语言的一致,除了基本类型其余的都是引用类型。引用类型主要有:类(class)、接口(interface)、数组、枚举(enum)、注解࿰…...

麦克风分类汇总
1.麦克风分类汇总 1)按声电转换原理分为:电动式(动圈式、铝带式),电容式(直流极化式)、压电式(晶体式、陶瓷式)、以及电磁式、碳粒式、半导体式等。 2)按声场作用力分为:…...

九龙证券|机制改革激发转融券活力 全面注册制释放两融展业新空间
在全面注册制准则规矩正式发布的同时,修订后的转融通事务规矩也应约与商场碰头。2月17日,中证金融发布《中国证券金融公司转融通事务规矩(试行)(2023年修订)》等规矩(简称“转融通新规”&#x…...

6——JVM调优工具详解及调优实战
Jmap、Jstack、Jinfo命令详解 Jmap 此命令可以用来查看内存信息,实例个数,以及占用内存大小 生成dump文件 把dump文件装入Jvisvalvm进行分析 Jstack Jstack加进程id查找死锁 Jstack找出占CPU最高的线程堆栈信息 top -p 进程号:显示进程…...

AcWing语法基础课笔记 第八章 C++ STL 第九章 位运算与常用库函数
第八章 C STL 第八章 C STL 1.#include <vector> 2.#include<queue> 3.#include <stack> 4.#include <deque> 5.#include <set> 6.#include<map> 第九章 位运算与常用库函数 STL是提高C编写效率的一个利器。 ——闫…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...