使用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编写效率的一个利器。 ——闫…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
