记录
最近在模仿简书来自己写接口,简单的CRUD就不说了,直到碰到了文章的多级评论,发现怎么设计SQL都只能实现二级评论,就是在在实体类里面加上下一级评论的属性。
解决方案
多级评论其实就是一个评论下可以再被评论,那么找所有评论不就是找到最上级评论,然后遍历最上级的评论,找到每一个上级评论的下级评论,整个过程其实就是一个简单的递归。
实现
这是我的数据库表
comment_status是标记这个评论是不是最上级评论(0:是,1:不是),parent_id是该评论的上级评论的id。
这是DTO对象
/**
* @Author: Ember
* @Date: 2021/4/13 20:18
* @Description: 评论
*/
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class CommentDto {
/**
* 主键
*/
private Integer id;
/**
* 评论者
*/
private Integer accountId;
/**
* 上级评论
*/
private Integer parentId;
/**
* 文章
*/
private Integer blogId;
/**
* 评论内容
*/
private String content;
/**
* 下级评论
*/
private List<CommentDto> sons;
/**
* 发生日期
*/
private String date;
}
设计两条SQL,一条是找最上级评论的,一条是根据上级评论来找下级评论的
<select id="getCommentByBlogId" resultType="CommentDto" parameterType="Integer">
SELECT bc.`blog_id` as blogId,bc.`parent_id` as parentId,bc.`id`,
bc.`account_id` as accountId,bc.`content` as content,bc.`create_time` as date
FROM blog_comment bc
WHERE bc.`blog_id` = #{blogId}
AND bc.`comment_status` = 0;
</select>
<select id="getCommentByParentId" resultType="CommentDto" parameterType="Integer">
SELECT bc.`blog_id` as blogId,bc.`id`,bc.`account_id` AS accountId,
bc.`content` AS content,
bc.`create_time` AS DATE,
bc.`parent_id` AS parentId
FROM blog_comment bc
WHERE bc.`parent_id` = #{parentId} AND bc.`comment_status` != 0;
</select>
Service层的递归实现
/**
* @Author: Ember
* @Date: 2021/4/13 20:37
* @Description:
*/
@Service
public class CommentServiceImpl implements CommentService {
private CommentMapper commentMapper;
@Autowired
public void setCommentMapper(CommentMapper commentMapper) {
this.commentMapper = commentMapper;
}
@Override
public List<CommentDto> getAllCommentByBlogId(Integer blogId) {
List<CommentDto> commentVos = this.commentMapper.getCommentByBlogId(blogId);
List<CommentDto> sons = getSons(commentVos);
return sons;
}
private List<CommentDto> getSons(Integer parentId){
return this.commentMapper.getCommentByParentId(parentId);
}
private List<CommentDto> getSons(List<CommentDto> parents){
if(parents == null || parents.size() == 0){
return null;
}
for (CommentDto parent : parents) {
int parentId = parent.getId();
List<CommentDto> sonCommentVos = getSons(parentId);
//递归找子评论
parent.setSons(getSons(sonCommentVos));
}
return parents;
}
此时只要前端传回文章的唯一标识blogId即可,下面是Controller层的代码
@GetMapping("/{blogId}")
public BaseResult getBlogById(@PathVariable("blogId") Integer blogId){
return DataResult.success(this.commentService.getAllCommentByBlogId(blogId));
}
使用Postman测试的结果
{
"code": "0",
"msg": "success",
"data": [
{
"id": 1,
"accountId": 2,
"parentId": 0,
"blogId": 1,
"content": "这是一级评论",
"sons": [
{
"id": 2,
"accountId": 2,
"parentId": 1,
"blogId": 1,
"content": "这是二级评论",
"sons": [
{
"id": 5,
"accountId": 2,
"parentId": 2,
"blogId": 1,
"content": "这是三级评论",
"sons": [
{
"id": 6,
"accountId": 2,
"parentId": 5,
"blogId": 1,
"content": "这是四级评论",
"sons": null,
"date": "2021-04-14 16:15:21"
}
],
"date": "2021-04-14 16:14:36"
}
],
"date": "2021-04-14 15:58:21"
},
{
"id": 3,
"accountId": 2,
"parentId": 1,
"blogId": 1,
"content": "这是二级评论",
"sons": null,
"date": "2021-04-14 16:14:02"
},
{
"id": 4,
"accountId": 2,
"parentId": 1,
"blogId": 1,
"content": "这是二级评论",
"sons": null,
"date": "2021-04-14 16:14:12"
}
],
"date": "2021-04-14 15:51:48"
}
]
}
反思
思考需求如何实现时,不要只想着依靠SQL一步完成,结合算法和数据结构往往可以解决很多难以解决的问题。