1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 博客-评论系统数据库设计及实现

博客-评论系统数据库设计及实现

时间:2023-12-08 14:05:52

相关推荐

博客-评论系统数据库设计及实现

前言

最近在开发一个小型个人博客,在数据库设计方面遇到了一些问题,这里做一个记录。

我设计的博客包含博客的基本信息包括标题内容等,此外还包括博客的标签类型评论

实际效果如下:

设计

一条博客下面对应多条一级父评论父评论下面又有子评论;每一条评论都有父评论(如果为第一级评论,则父评论为空),也有子评论(子评论可为空)。两种评论都有共同的评论博客ID。这里暂且忽略评论用户的ID等信息。

对象关系

一篇博客可能对应多条评论,一条评论只能对应一篇博客。(一对多)

一条父评论可能包含多条子评论,一条子评论只能对应一条父评论。(一对多)

上面关系里:

博客和评论(一对多)的关系可以在评论表中使用外键关联博客表的博客id主键父评论和子评论(一对多)的关系可以在评论表中使用

注意:在企业开发中,在面对大量数据、表、并发的需求时,应当避免使用外键来关联表;关联关系应当在业务层进行处理与判断,详情可参考【IT老齐012】阿里开发规范解读:为啥禁用外键约束?,这里我为了简单明了,使用主键来关联。

关系图

这里放一个关系图,用来理解上述的对象之间的关系

难点:如何在持久层存储这种关系(评论),然后在业务层高效表示出来?

持久层最佳的实践是评论表存储每一条评论信息,每条评论都有一个主键comment_id,每一条评论使用外键parent_id关联同一张表里的父评论的comment_id,再用外键blog_id关联博客表里的博客blog_id

主要方案:

采用树形结构(类似二叉树)采用二维结构(只有第一层和第二层的嵌套)

这里我才用“二维结构”,主要原因还是实现简单。

注意:以下的实现介绍都是在“二维结构”的实现方式上设计的,树形结构也可用这个实现,只是在处理从数据库中读取评论时操作不同(代码中有体现)。

评论表

这里我还添加了一个属性root_parent,主要用来存储一条评论的最顶层的父评论的id,而parent_id用来存储直接关联的父评论。这么做的主要原因:

我的业务层采用的是二维结构,存储一个根节点id方便处理数据,提高效率。未来需要删除评论时可以删除comment_id=x的评论以及所有root_parent=x的子评论。

Java实体类

Blog

这里省略了无关的字段,只专注于博客、评论

@Datapublic class Blog {/** 博客自身属性 **/private Integer blog_id; // 博客IDprivate String title;// 标题private String content; // 内容/** 实体关系 **/private List<Comment> comments = new ArrayList<>(); // 博客评论(一对多)// 构造器 ...}

Comment

这里省略了无关的字段,只专注于评论

@Datapublic class Comment {/** 自身属性 **/private Integer comment_id;// 评论idprivate String content;// 评论内容/** 实体关系 **/private Integer blog_id;// 这里考虑到实际情况下:因为blog本身可能很大,在前后端传输数据时会影响效率,所以comment没必要再包含blogprivate Integer parent_id; // 父评论的id(被回复的评论)private Integer root_parent_id; // 根评论的id(最顶级的评论)private List<Comment> child; // 本评论下的子评论 // 构造器 ...}

注意:

1.如果采用“树型结构”中,那么评论都需严格按照父子关系存储,子评论都存储在父评论的child中,子评论通过parent_id来找到其直接关联的父评论,通过root_parent来找到根评论。

2. 二维结构中,只有根评论(一级评论)的child中存储子评论,所有子评论(二级评论)的child中都不再存储其子评论。(这就是为什么二维结构实现起来相对简单一些的原因)

MySQL建表

-- 博客SET FOREIGN_KEY_CHECKS = 0; DROP TABLE IF EXISTS `blog`;create table `blog`(`blog_id` int NOT NULL AUTO_INCREMENT,`title` varchar(64) NOT NULL,`content` mediumtext NOT NULL,PRIMARY KEY (`blog_id`))ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 评论SET FOREIGN_KEY_CHECKS = 0; DROP TABLE IF EXISTS `comment`;create table `comment`(`comment_id` int NOT NULL AUTO_INCREMENT,`content` text NOT NULL,`blog_id` int NOT NULL, `parent_id` int DEFAULT NULL,`root_parent_id` int DEFAULT NULL,PRIMARY KEY (`comment_id`),CONSTRAINT FOREIGN KEY (`blog_id`) REFERENCES `blog`(`blog_id`) ON DELETE CASCADE ON UPDATE CASCADE,CONSTRAINT FOREIGN KEY (`parent_id`) REFERENCES `comment`(`comment_id`) ON DELETE CASCADE ON UPDATE CASCADE,CONSTRAINT FOREIGN KEY (`root_parent_id`) REFERENCES `comment`(`comment_id`) ON DELETE CASCADE ON UPDATE CASCADE)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

同样的,这里省略了无关的属性,只专注于博客、评论的核心字段。

从数据库读取评论

按照现有数据库中存储的数据,无法直接(dao层数据库取出)达到如下效果:

这里我参考了评论系统数据库设计及实现的实现方案,在Service层对数据进行处理达到了理想效果,且时间复杂度控制在O(n)

大体思路:

从数据库读取评论处理评论之间的嵌套关系返回结果

具体步骤:

查询评论表,取出所有blog_id=给定的blog_id的评论遍历“步骤1”中查询到的评论,把所有评论加入HashMap方便以O(1)的时间复杂度取得,如果是父评论就直接加入最终的result(最终返回的就是result)再次遍历“步骤1”中查询到的评论,如果当前评论有父评论,就通过“步骤2”中的HashMap取得父评论;再把当前评论加入到父评论的子评论列表。(如果需要构建树型结构评论,需要一直嵌套添加子评论至父评论的child中;如果需要构建二维结构评论,则只添加一层即可)返回给调用方

CommentUtils

public static List<Comment> processComments(List<Comment> list, boolean useTree) {// 空间换时间:把comment_id和comment放入map,能够快速取到CommentMap<Integer, Comment> map = new HashMap<>();// 最终要返回的listList<Comment> result = new ArrayList<>();// 遍历一次,把所有comment加入到map中,方便快速查找映射// 如果是父评论,就直接放入result列表for (Comment comment : list) {if (comment.getParent_id() == null) {result.add(comment);}map.put(comment.getComment_id(), comment);}// 再次遍历,子评论放入到父评论的child中for (Comment comment : list) {// 为子评论设置父评论的nickname和email,以用于前端展示if (comment.getParent_id() != null) {Comment parentComment = map.get(comment.getParent_id());comment.setParent_nickname(parentComment.getNickname());comment.setParent_email(parentComment.getEmail());}Integer id;// 如果是以树形结构构建子评论,需要获取的id就是parent_id// 如果是以其它方式(二维数组),需要获取的id就是root_parent_idif (useTree) {id = comment.getParent_id();} else {id = comment.getRoot_parent_id();}// 只要存在父评论/根父评论if (id != null) {// 直接通过map快速取得评论,并把自己加入到父评论的子集Comment p = map.get(id);if (p.getChild() == null) {p.setChild(new ArrayList<>());}p.getChild().add(comment);}}return result;}

CommentServiceImpl

/*** 查询一条博客下的所有评论* @param blog_id* @return 嵌套父子关系的博客列表*/@Overridepublic List<Comment> getComments(int blog_id, boolean useTree) {// 读取评论blog_id为blog_id的数据List<Comment> list = commentMapper.getComments(blog_id);// 处理并构建评论;useTree:是否使用树形结构的评论(false则使用二维结构)return CommentUtils.processComments(list, useTree);}

最后,在查询博客时,可将查询到的评论加入到某一条博客实例中;也可将评论和博客分离。在我的个人网站中采用的方法是,博客模块和评论模块分开请求服务器,并且独立加载,互不干扰。

树形结构:类似实现效果如下(图片来自引用2):

二维结构:实现效果如下

插件

前段时间发现一个博客评论模块的插件,UI看起来很好看,功能也很全很强大,分享一下:

官网:Disqus

放在博客里是这样的:

参考:

SpringBoot开发一个小而美的个人博客评论系统数据库设计及实现

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。