使用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编写效率的一个利器。 ——闫…...
作业本耐用度差距巨大?深圳大明印刷厂拆解合规工艺,告别定制作业本掉页开裂通病
在校园日常教学中,很多学校都会遇到同一个难题:同一学期采购的作业本、定制作业本,品质差距悬殊,有的完好无损用到期末,有的短短几周就出现书脊开裂、页面脱落、边角破损、翻页卡顿等问题。不少人误以为是学生使用习惯…...
零基础轻松拿捏!魔珐星云青少年健康运动教学数字人搭建全流程指南
大家好!本次给大家分享一款面向青少年体育教育的AI创意实践项目——青少年健康运动教学智能数字交互系统。本项目聚焦青少年体质健康痛点,围绕体育教学智能化升级需求,打造集健康知识教学、运动动作陪练、健康知识考核、运动能力评测于一体的…...
告别虚频困扰:用VASP+DynaPhoPy搞定高温材料声子谱的保姆级教程
高温材料声子谱计算实战:从虚频困境到非谐解决方案 引言:虚频问题的根源与突破路径 在计算材料学领域,声子谱分析是理解材料动力学稳定性和热力学性质的核心手段。然而许多研究者都遭遇过这样的困境:对实验合成的材料进行简谐近似…...
新能源车轻量化为什么开始盯上高强镁合金?
续航,是悬在每一台纯电动汽车头上的达摩克利斯之剑。多充一度电、多堆一些正极材料,是一条路;但还有另一条路——把车造得更轻。 SAE(美国汽车工程师学会)的测算已经被反复引用:整车每减重100千克ÿ…...
CANoe诊断测试没CDD文件怎么办?手把手教你用Fault Memory窗口和CAPL脚本读取解析DTC故障码
CANoe诊断测试无CDD文件的实战解决方案:从Fault Memory到CAPL脚本全解析当CDD文件缺失或定义不清晰时,诊断测试工程师常常陷入困境。本文将深入探讨如何利用Fault Memory窗口的基础功能,并通过CAPL脚本实现更灵活、更强大的故障码读取与解析方…...
特定任务需求场景下的过约束并联机构构型设计与控制方法【附代码】
✨ 长期致力于曲面加工、构型综合、运动学和动力学建模、性能评价、多目标优化、滑模控制、鲁棒控制、视觉传感技术研究工作,擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流,点击《获取方式》 (…...
保姆级教程:在Windows 10上用QEMU+Kylin搭建可内外网访问的完整开发环境
在Windows 10上构建QEMUKylin全功能开发环境的终极指南当开发者需要在本地快速搭建一个隔离的国产操作系统开发环境时,QEMU虚拟化方案配合银河麒麟系统能提供高度灵活的沙箱体验。本文将手把手带你完成从零配置到内外网联通的完整工作流,涵盖虚拟化环境部…...
实战对比:用直方图均衡化与CLAHE拯救你的背光/过曝照片(附Python完整代码)
拯救逆光废片:直方图均衡化与CLAHE的实战效果对比每次旅行回来整理照片时,总会有几张因为光线问题几乎要删除的废片——要么是逆光下的人脸黑得看不清五官,要么是天空过曝失去所有云层细节。这些照片往往记录着重要时刻,直接删除实…...
基于PGA2311的树莓派Hi-Fi模拟音量控制器设计与实现
1. 项目概述:为树莓派DAC打造的高品质模拟音量控制器玩过树莓派音频播放器的朋友都知道,用上像PCM1794A这类高性能DAC芯片后,音质确实能上一个台阶,但有个不大不小的麻烦:这类芯片本身不带音量控制。软件调音量&#x…...
SpeakingURL版本升级指南:从旧版本迁移到最新版本的完整教程
SpeakingURL版本升级指南:从旧版本迁移到最新版本的完整教程 【免费下载链接】speakingurl Generate a slug – transliteration with a lot of options 项目地址: https://gitcode.com/gh_mirrors/sp/speakingurl SpeakingURL是一款强大的URL友好化工具&…...
