使用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编写效率的一个利器。 ——闫…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...
解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...
消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...
QT开发技术【ffmpeg + QAudioOutput】音乐播放器
一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下,音视频内容犹如璀璨繁星,点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频,到在线课堂中知识渊博的专家授课,再到影视平台上扣人心弦的高清大片,音…...
