評論系統數據庫設計及實現
需求分析
一般我們瀏覽網站的時候經常能看到如下圖的這種效果(圖片來自CSDN)
這種評論層層嵌套,每個評論下面還掛着若干個對評論的回復。
這種結構類似於樹狀結構,用戶看起來一目了然,也是一種非常主流的評論系統設計。
數據庫設計
在以評論為主的樹形結構中,數據庫的設計非常靈活,可以是單表設計,每個評論都有一個parent_id指向父評論。還可以分開為兩個表,評論一張表,對評論的回復是另一張表。
這里我使用的是單表設計。
數據表設計如下。由於我開發的是一個新聞系統,所以我就直接以項目舉例。
表字段 | 字段說明 |
---|---|
commentId | 評論的id,自增值,每個評論都對應一個唯一的commentId |
newsId | 評論所對應的新聞的id |
content | 評論的內容 |
userId | 發出該評論用戶的id |
parentId | 指向父評論的id,如果不是對評論的回復,那么該值為null |
date | 評論產生日期 |
SQL語句:
評論表:
create table if not exists comments
(
commentId bigint auto_increment primary key,
newsId bigint not null,
parentId bigint,
content text not null,
userId bigint not null,
date timestamp default current_timestamp(),
foreign key (parentID) references comments (commentId),
foreign key (userID) references users (userId),
foreign key (newsID) references news (newsId)
) charset = utf8mb4;
實現
- 查詢語句:
SELECT a.commentId,a.newsId,a.parentId,a.newsId,b.nickname,b.avatar,a.content,a.date
FROM comments AS a,users AS b WHERE a.newsId=#{newsId} AND a.userId=b.userId
為了減少數據庫查詢次數,直接一次將一個新聞下的所有評論都查詢了出來,然后通過程序來編排評論的顯示結構。通過適當的冗余來提高性能也是常用的優化手段之一
- 評論的實體類
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class Comment {
Long commentId;
Long newsId;
Long parentId;
Long userId;
String nickname;
String avatar;
String content;
Date date;
List<Comment> child;
}
這里給出一段通過程序來組織所有評論的代碼(為了文章的精簡,只寫邏輯相關的代碼)
public List<Comment> getComments(Long newsId) {
List<Comment> allComments = commentMapper.getComments(newsId);
if (allComments == null || allComments.size() == 0) {
return new ArrayList<>();
}
List<Comment> comments = new ArrayList<>();
List<Comment> parents = new ArrayList<>();
for (Comment comment : allComments) {
if (comment.getParentId()==null) {
comments.add(comment);
parents.add(comment);
} else {
boolean foundParent=false;
for (Comment parent : parents) {
if (comment.getParentId() == parent.getCommentId()) {
if (parent.getChild() == null) {
parent.setChild(new ArrayList<>());
}
parent.getChild().add(comment);
parents.add(comment);
foundParent=true;
//如果對list迭代過程中同時修改list,會報java.util.ConcurrentModificationException 的異常,所以我們需要break,當然break也可以提高算法效率
break;
}
}
if (!foundParent) {
throw new RuntimeException("can not find the parent comment");
}
}
}
return comments;
上面的算法有兩個缺點:
- 時間復雜度較高:O(n^2)
- 依賴一個前提條件,即allComments數組中所有的Comment是按照時間升序排列的。
實際上,這是一類典型的扁平數據樹形化的問題,我們還可以進一步優化到最好O(n)的復雜度
public List<Comment> getComments(Long newsId) {
List<Comment> all = new ArrayList<>();
Map<Long, Comment> map = new HashMap<>();
List<Comment> result = new ArrayList<>();
for (Comment c : all) {
if (c.parentId == null) {
result.add(c);
}
map.put(c.commentId, c);
}
for (Comment c : all) {
if (c.parentId != null) {
Comment parent = map.get(c.parentId);
if (parent.child == null) {
parent.child = new ArrayList<>();
}
parent.child.add(c);
}
}
return result;
}
最終形成的效果圖。
接口返回的數據如下:
{
"code": "success",
"message": "獲取評論成功",
"status": "200",
"data": [
{
"id": "236051",
"author_name": "Jianbo",
"author_url": "https://wx.qlogo.cn/mmopen/vi_32/Qib5jkFMntPJnT8b2nyzKicoYSuXLeyl07ia1dianxx1fWcic9hJL4UOEuIJvoWWbx7IFia3olUGqiabZvTe0dmeFBicHQ/132",
"date": "6小時前",
"content": "tt",
"userid": "24",
"child": []
},
{
"id": "236028",
"author_name": "起航",
"author_url": "https://wx.qlogo.cn/mmopen/vi_32/7Aq39lKL2jxoWSMgbiaYkQzOR0mOMTm2TLjVhRicYaFXAzg20I8gpcqySYYYQMWG60p8r5kibG3ibiav3CC8Bzibjblw/132",
"date": "2019-04-11",
"content": "很朴實的文字,又讓人感動唏噓",
"formId": null,
"userid": "9676",
"child": [
{
"id": "236032",
"author_name": "Jianbo",
"author_url": "https://wx.qlogo.cn/mmopen/vi_32/Qib5jkFMntPJnT8b2nyzKicoYSuXLeyl07ia1dianxx1fWcic9hJL4UOEuIJvoWWbx7IFia3olUGqiabZvTe0dmeFBicHQ/132",
"date": "2天前",
"content": ":-)",
"userid": "24",
"child": [
{
"id": "236040",
"author_name": "God loves me",
"author_url": "https://wx.qlogo.cn/mmopen/vi_32/QTU6iasloiaun5OX6ZcZB964vhHLAc5RuIf8kMR3nwIXvy0HibYOe9RJ9o8escDOIj7MB1vica5ibZ2XSDXIibfQMsJA/132",
"date": "1天前",
"content": "為什么有人會選擇安樂死呢,活着難道比不上痛苦嗎",
"userid": "9663",
"child": [
{
"id": "236042",
"author_name": "Jianbo",
"author_url": "https://wx.qlogo.cn/mmopen/vi_32/Qib5jkFMntPJnT8b2nyzKicoYSuXLeyl07ia1dianxx1fWcic9hJL4UOEuIJvoWWbx7IFia3olUGqiabZvTe0dmeFBicHQ/132",
"date": "1天前",
"content": "如果無法有尊嚴的活着,就難受",
"child": []
}
]
}
]
}
]
},
{
"id": "236024",
"author_name": "倡萌",
"author_url": "../../images/gravatar.png",
"date": "2019-04-11",
"content": "每個人都有自己難以忘懷的過往,昨天今天明天,努力過好每一天!",
"userid": "0",
"child": [
{
"id": "236041",
"author_name": "Jianbo",
"author_url": "https://wx.qlogo.cn/mmopen/vi_32/Qib5jkFMntPJnT8b2nyzKicoYSuXLeyl07ia1dianxx1fWcic9hJL4UOEuIJvoWWbx7IFia3olUGqiabZvTe0dmeFBicHQ/132",
"date": "1天前",
"content": "過好今天,很重要",
"userid": "24",
"child": []
}
]
},
{
"id": "236018",
"author_name": "Jielinfan",
"author_url": "https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJBXIvvpMo5nXdlk6Mxwia9chS9E8VHGEQbDmyEAx8opRibztDzmpGHpbC3lR5vh8l4fsScZWoyEWyQ/132",
"date": "2019-04-08",
"content": "祝福老哥。",
"userid": "280",
"child": [
{
"id": "236019",
"author_name": "Jianbo",
"author_url": "https://wx.qlogo.cn/mmopen/vi_32/Qib5jkFMntPJnT8b2nyzKicoYSuXLeyl07ia1dianxx1fWcic9hJL4UOEuIJvoWWbx7IFia3olUGqiabZvTe0dmeFBicHQ/132",
"date": "2019-04-09",
"content": ":-)",
"userid": "24",
"child": []
}
]
},
{
"id": "236017",
"author_name": "增大網",
"author_url": "../../images/gravatar.png",
"date": "2019-04-08",
"content": "送你一片大海,讓你一帆風順;送你一個太陽,讓你熱情奔放;送你一份真誠,祝你開心快樂;送你一份祝福,讓你快樂天天!",
"formId": null,
"userid": "0",
"child": []
},
{
"id": "236011",
"author_name": "今日新聞",
"author_url": "../../images/gravatar.png",
"date": "2019-04-07",
"content": "文章不錯非常喜歡",
"userid": "0",
"child": [
{
"id": "236052",
"author_name": "Jianbo",
"author_url": "https://wx.qlogo.cn/mmopen/vi_32/Qib5jkFMntPJnT8b2nyzKicoYSuXLeyl07ia1dianxx1fWcic9hJL4UOEuIJvoWWbx7IFia3olUGqiabZvTe0dmeFBicHQ/132",
"date": "6小時前",
"content": "謝謝",
"userid": "24",
"child": []
}
]
}
]
}