Mybatis的递归查询实现二级评论



最近闲来无事,在做毕业设计。前台页面需要一个评论功能,感觉三方的评论太复杂,功能也太多。就想着自己写一个简单点的,本人比较菜,方法比较笨,可能效率不高。

环境介绍

后端:SpringBoot + Mybatis

前端:Vue + Element Plus

效果展示

Mybatis的递归查询实现二级评论

数据库设计

这里展示的是需要登录才能使用评论(依赖用户表),如果不需要登录,可删除 comment_user_id,添加邮箱和昵称字段: emailnickname

1、评论表字段说明

字段名 类型 说明 id bigint 主键 article_id bigint 评论所在文章ID parent_comment_id bigint 父评论id,null表示一级评论 comment_user_id bigint 评论发布用户ID content longtext 评论内容 create_time datetime 创建时间

只展示最基本的字段,需要字段自行添加,例如: versiondeleted

2、评论表建表语句

CREATE TABLE comment (
  id bigint(20) NOT NULL COMMENT '评论ID',
  article_id bigint(20) DEFAULT NULL COMMENT '文章ID',
  comment_user_id bigint(20) DEFAULT NULL COMMENT '评论者ID',
  parent_comment_id bigint(20) DEFAULT NULL COMMENT '父评论ID',
  content longtext COMMENT '评论内容',
  create_time datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (id) USING BTREE,
  KEY comment_user (comment_user_id),
  KEY comment_article (article_id),
  KEY child_parent (parent_comment_id),
  CONSTRAINT child_parent FOREIGN KEY (parent_comment_id) REFERENCES comment (id),
  CONSTRAINT comment_article FOREIGN KEY (article_id) REFERENCES article (id),
  CONSTRAINT comment_user FOREIGN KEY (comment_user_id) REFERENCES user (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3、其他表就不展示了,下面有源码案例

后端开发

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class Comment {
    private Long id;
    private Long articleId;
    private Long parentCommentId;
    private String content;
    private Date createTime;
    private Date updateTime;
    private User commentUser;
    private Comment parentComment;
    private List replayComments = new ArrayList<>();
}
  • commentUser:评论用户,一对一映射
  • parentComment:父评论,一对一映射,这里你也可以直接用parentCommentId
  • replayComments:子评论,一对多映射

Mapper

mapper文件

我们来看看, SQL语句怎么写,还是比较复杂的,因为考虑到映射,关联的表比较多


        select c.*,cu.avatar c_u_avatar, cu.nickname c_u_nickname, pu.id p_u_id, pu.avatar p_u_avatar, pu.nickname p_u_nickname
        from ((comment c LEFT JOIN comment p on p.id = c.parent_comment_id) LEFT JOIN user cu on cu.id = c.comment_user_id) LEFT JOIN user pu on pu.id = p.comment_user_id
        where c.parent_comment_id = #{id} order by create_time

        select c.*, u.avatar u_avatar, u.nickname u_nickname
        from comment c left join user u on u.id = c.comment_user_id
        where c.article_id=#{articleId} and parent_comment_id is null

这里使用了很多 LEFT JOIN,可以替换成同意思的 WHERE

mapper类

@Repository
public interface CommentMapper {

    // 列出所有一评论
    List listTopComment(@Param("articleId") Long articleId);

    //列出一级评论下子评论一级子评论的子评论
    List listTreeComment(@Param("id") Long id);

}

Service

CommentService

public interface CommentService {
    List listTopComment(Long articleId);
}

CommentServiceImpl

@Service
ublic class CommentServiceImpl implements CommentService {

    final CommentMapper commentMapper;
    public CommentServiceImpl(CommentMapper commentMapper) {
        this.commentMapper = commentMapper;
    }

    @Override
    public List listTopComment(Long articleId) {
        // 获取所有一级评论
        List topComment = commentMapper.listTopComment(articleId);
        // 获取所有子评论
        for (Comment comment : topComment) {
            comment.setReplayComments(commentMapper.listTreeComment(comment.getId()));
        }
        return topComment;
    }
}

Controller

@RestController
@RequestMapping("/comment")
public class CommentController {

    final CommentService commentService;
    public CommentController(CommentService commentService) {
        this.commentService = commentService;
    }

    @GetMapping("/list/{articleId}")
    List listComment(@PathVariable("articleId") Long articleId) {
        return commentService.listTopComment(articleId);
    }
}

测试

弄几条数据

文章数据
insert into article(id, title, content) value(1, '测试', '测试测试测试测试测试测试测试')

用户数据
insert into user(id, nickname, avatar) value(1, '测试员1', '/images/avatar01.jpg')
insert into user(id, nickname, avatar) value(2, '测试员2', '/images/avatar02.jpg')

#评论数据
insert into comment(id, article_id, parent_comment_id, comment_user_id, content,create_time)
value(1, 1, null, 1, '她好美', NOW())
insert into comment(id, article_id, parent_comment_id, comment_user_id, content,create_time)
value(2, 1, 1, 2, '你那是馋她身子', NOW())
insert into comment(id, article_id, parent_comment_id, comment_user_id, content,create_time)
value(3, 1, 2, 2, '哈哈', NOW())

分析结果

[
    {
        "id": 1,
        "articleId": 1,
        "parentCommentId": null,
        "content": "她好美",
        "createTime": "2021-09-30T11:06:19.000+00:00",
        "updateTime": null,
        "commentUser": {
            "id": 1,
            "nickname": "测试员1",
            "avatar": null
        },
        "parentComment": null,
        "replayComments": [
            {
                "id": 2,
                "articleId": 1,
                "parentCommentId": null,
                "content": "你那是馋她身子",
                "createTime": "2021-09-30T11:07:25.000+00:00",
                "updateTime": null,
                "commentUser": {
                    "id": 2,
                    "nickname": "测试员2",
                    "avatar": "/images/avatar02.jpg"
                },
                "parentComment": {
                    "id": 1,
                    "articleId": null,
                    "parentCommentId": null,
                    "content": null,
                    "createTime": null,
                    "updateTime": null,
                    "commentUser": {
                        "id": 1,
                        "nickname": "测试员1",
                        "avatar": "/images/avatar01.jpg"
                    },
                    "parentComment": null,
                    "replayComments": []
                },
                "replayComments": [
                    {
                        "id": 3,
                        "articleId": 1,
                        "parentCommentId": null,
                        "content": "哈哈",
                        "createTime": "2021-09-30T11:36:24.000+00:00",
                        "updateTime": null,
                        "commentUser": {
                            "id": 2,
                            "nickname": "测试员2",
                            "avatar": "/images/avatar02.jpg"
                        },
                        "parentComment": {
                            "id": 2,
                            "articleId": null,
                            "parentCommentId": null,
                            "content": null,
                            "createTime": null,
                            "updateTime": null,
                            "commentUser": {
                                "id": 2,
                                "nickname": "测试员2",
                                "avatar": "/images/avatar02.jpg"
                            },
                            "parentComment": null,
                            "replayComments": []
                        },
                        "replayComments": []
                    }
                ]
            }
        ]
    }
]

我们可以看到,数据是嵌套的,这种套娃格式也是一种方案

[
    {
        "id": 1,
        "content": "她好美",
        "...": ...,
        "replayComments": [
            {
                "id": 2,
                "...": ...,
                "replayComments": [
                    {
                        "id": 3,
                "...": ...,
                "replayComments": null,
                    }
                ]
            }
        ]
    }
]

改套娃为二级

将套娃评论改为二级,只需要在逻辑部分将二级评论的子评论(三级)等等(四级,五级…),全部改为二级评论

Service

@Service
public class CommentServiceImpl implements CommentService {

    int commentNum = 0;
    final CommentMapper commentMapper;
    public CommentServiceImpl(CommentMapper commentMapper) {
        this.commentMapper = commentMapper;
    }

    @Override
    public List listTopComment(Long articleId) {
        // 获取所有一级评论
        List topComment = commentMapper.listTopComment(articleId);
        // 获取所有子评论
        for (Comment comment : topComment) {
            comment.setReplayComments(commentMapper.listTreeComment(comment.getId()));
        }
        return eachComment(topComment);
    }

    @Override
    public List listComment(Long articleId) {
        commentNum = 0;
        // 获取所有一级评论
        List topComment = commentMapper.listTopComment(articleId);
        // 获取所有子评论
        for (Comment comment : topComment) {
            comment.setReplayComments(commentMapper.listTreeComment(comment.getId()));
        }
        return eachComment(topComment);
    }

    // 循环遍历顶级评论,并放入commentView中
    private List eachComment(List comments) {
        List commentView = new ArrayList<>();
        for (Comment comment : comments) {
            Comment c = new Comment();
            BeanUtils.copyProperties(comment, c);
            commentView.add(c);
        }
        //合并评论的各层子代到一级子代中
        combineChildren(commentView);
        return commentView;
    }

    // 进行所有子评论的遍历,并添加到顶级评论的子评论中
    private void combineChildren(List comments) {
        for (Comment comment : comments) {
            commentNum++;
            List replys1 = comment.getReplayComments();
            for (Comment reply1 : replys1) {
                //循环迭代找出子代,存放在tampReplys中
                recursively(reply1);
            }
            //修改顶级节点的reply集合为迭代后的集合
            comment.setReplayComments(tempReplys);
            //清除临时存放区
            tempReplys = new ArrayList<>();
        }
    }

    //存放迭代找出的所有子代集合
    private List tempReplys = new ArrayList<>();

    // 递归,将顶级评论的子评论以及子评论的子评论全部放到 tempReplys
    private void recursively(Comment comment) {
        tempReplys.add(comment);//顶节点添加到临时存放区
        commentNum++;
        if (comment.getReplayComments().size() > 0) {
            List replys = comment.getReplayComments();
            for (Comment reply : replys) {
                if (reply.getReplayComments().size() > 0) {
                    recursively(reply);
                } else {
                    commentNum++;
                    tempReplys.add(reply);
                }
            }
        }
    }
}

commentNum 记录了评论数量,有需要的可以返回给前端

测试

Controller调用 listComment方法

[
    {
        "id": 1,
        "articleId": 1,
        "parentCommentId": null,
        "content": "她好美",
        "createTime": "2021-09-30T11:06:19.000+00:00",
        "updateTime": null,
        "commentUser": {
            "id": 1,
            "nickname": "测试员1",
            "avatar": null
        },
        "parentComment": null,
        "replayComments": [
            {
                "id": 2,
                "articleId": 1,
                "parentCommentId": null,
                "content": "你那是馋她身子",
                "createTime": "2021-09-30T11:07:25.000+00:00",
                "updateTime": null,
                "commentUser": {
                    "id": 2,
                    "nickname": "测试员2",
                    "avatar": "/images/avatar02.jpg"
                },
                "parentComment": {
                    "id": 1,
                    "articleId": null,
                    "parentCommentId": null,
                    "content": null,
                    "createTime": null,
                    "updateTime": null,
                    "commentUser": {
                        "id": 1,
                        "nickname": "测试员1",
                        "avatar": "/images/avatar01.jpg"
                    },
                    "parentComment": null,
                    "replayComments": []
                },
                "replayComments": [
                    {
                        "id": 3,
                        "articleId": 1,
                        "parentCommentId": null,
                        "content": "哈哈",
                        "createTime": "2021-09-30T11:36:24.000+00:00",
                        "updateTime": null,
                        "commentUser": {
                            "id": 2,
                            "nickname": "测试员2",
                            "avatar": "/images/avatar02.jpg"
                        },
                        "parentComment": {
                            "id": 2,
                            "articleId": null,
                            "parentCommentId": null,
                            "content": null,
                            "createTime": null,
                            "updateTime": null,
                            "commentUser": {
                                "id": 2,
                                "nickname": "测试员2",
                                "avatar": "/images/avatar02.jpg"
                            },
                            "parentComment": null,
                            "replayComments": []
                        },
                        "replayComments": []
                    }
                ]
            },
            {
                "id": 3,
                "articleId": 1,
                "parentCommentId": null,
                "content": "哈哈",
                "createTime": "2021-09-30T11:36:24.000+00:00",
                "updateTime": null,
                "commentUser": {
                    "id": 2,
                    "nickname": "测试员2",
                    "avatar": "/images/avatar02.jpg"
                },
                "parentComment": {
                    "id": 2,
                    "articleId": null,
                    "parentCommentId": null,
                    "content": null,
                    "createTime": null,
                    "updateTime": null,
                    "commentUser": {
                        "id": 2,
                        "nickname": "测试员2",
                        "avatar": "/images/avatar02.jpg"
                    },
                    "parentComment": null,
                    "replayComments": []
                },
                "replayComments": []
            }
        ]
    }
]

分析一波

可以看到 id为的 3 的评论已经成为二级评论了

[
    {
        "id": 1,
        "content": "她好美",
        "...": ...,
        "replayComments": [
            {
                "id": 2,
                "...": ...,
                "replayComments": [
                    {
                        "id": 3,
                "...": ...,
                "replayComments": [],
                    }
                ]
            },
        {
                "id": 3,
                "...": ...,
                "replayComments": []
            }
        ]
    }
]

源码

码云地址

先到这。。。

Mybatis的递归查询实现二级评论

Original: https://www.cnblogs.com/sw-code/p/15358093.html
Author: sw-code
Title: Mybatis的递归查询实现二级评论

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/585586/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Linux下启动/关闭Oracle

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Linux 2022年8月26日
    0246
  • WPF获取系统dpi

    WPF获取系统dpi var dpiX = (int)typeof(SystemParameters).GetProperty("DpiX", BindingF…

    Linux 2023年6月13日
    034
  • GFS-Google 文件系统

    GFS分布式文件系统 简介 GFS是一个可扩展的分布式文件系统,用于大型的、分布式的、对大量数据进行访问的应用。它运行于廉价的普通硬件上,并提供容错功能。它可以给大量的用户提供总体…

    Linux 2023年6月13日
    037
  • c++ 使用shell命令

    #include #include #include #include #include //execute shell command //执行&#x…

    Linux 2023年5月28日
    041
  • 数字图像处理

    1. 图像的基本概念 连续图像:二维坐标系上连续变化的图像,图像的像点无限稠密。 离散图像:用数字序列表示的图像,像素是组成图像的基本单位。 1.1 图像数字化采样 图像经过采样与…

    Linux 2023年6月14日
    028
  • windows下设置redis开机自启动

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Linux 2022年9月14日
    0174
  • update-alternatives使用,更改Linux中软件默认版本

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Linux 2022年8月20日
    0235
  • linux编译安装nginx

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Linux 2022年9月10日
    0137
  • DirectX 使用 Vortice 从零开始控制台创建 Direct2D1 窗口修改颜色

    本文将告诉大家如何使用 Vortice 底层库从零开始,从一个控制台项目,开始搭建一个最简单的使用 Direct2D1 的 DirectX 应用。本文属于入门级博客,期望本文能让大…

    Linux 2023年6月6日
    040
  • 2022.19 linux中的危险命令

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Linux 2022年8月8日
    0258
  • window.parent、window.top、window.self

    在应用有frameset或者iframe的页面时,parent是父窗口,top是最顶级父窗口(有的窗口中套了好几层frameset或者iframe),self是当前窗口。 1.wi…

    Linux 2023年6月7日
    035
  • 2021年3月-第02阶段-前端基础-Flex 伸缩布局-移动WEB开发_flex布局

    移动web开发——flex布局 1.0 传统布局和flex布局对比 1.1 传统布局 兼容性好 布局繁琐 局限性,不能再移动端很好的布局 1.2 flex布局 操作方便,布局极其简…

    Linux 2023年6月8日
    041
  • Ubuntu 18.04:磁盘读取性能不佳

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Linux 2022年8月26日
    0243
  • ETCD分布式存储部署

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Linux 2022年11月8日
    0165
  • Linux-027-Centos JDK 环境离线安装配置

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Linux 2022年8月26日
    0261
  • Linux文件读、写、执行权限

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    Linux 2022年8月26日
    0341
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球