前提:本文旨在学习mongoDB对系统评论功能的实现思路,并不是完整的实现系统的评论功能。
_id | mongoDB文档默认编号 |
---|---|
cid | 文章id,有雪花算法生成 |
content | 评论内容 |
publishdate | 评论发布时间 |
userId | 评论人 |
articleId | 评论所属文章ID |
thumbup | 评论被点赞数 |
parentId | 0表示评论文章;若是评论的是评论则为被评论的评论c_id |
用户能对文章进行评论也能对评论进行评论,类似于树形结构。所以存在评论父级ID:parentId,当用户评论的是文章时,parentId=0;当用户评论的是别人的评论是,parentId = 被评论数据的ID
单个查询好一些。在文章页面中,先只查询属于文章的评论,也就是parentId=0的评论数据。因为,评论的评论并不是所有的人都想看,若想看,想看的点开单独查询即可(使用异步加载),且全部查出来的方式递归效率低,可能导致系统性能差。
从系统性能与用户体验考虑,单个查询较好。
使用Spring-Data-MongoDB
<!--spring data mongodb--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency>
本人将mongodb部署在docker容器中
ip:192.168.200.135
端口为默认端口:27017
配置文件配置
spring: data: mongodb: host: 192.168.200.135 #连接ip port: 27017 #端口号 database: commentDB #数据库
package cn.jankin.pojo; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import java.io.Serializable; import java.util.Date; /** * 评论实体 */ @Document("comment") public class Comment implements Serializable { @Id private String _id; //mongodb默认主键,加快数据的插入 private String cid; //雪花生成器生成评论标识,mongoDB建立索引 private String articleid; private String content; private String userid; private String parentid; private Date publishdate; private Integer thumbup; }
注意:在这里我并没有将mongodb默认的 _id 作为评论的标识,而是分配另一个属性 cid作为标识。
原因:优化mongodb的性能,解决可能发生的数据不一致问题。
意义:系统内每一分每一秒都有可能有用户发布评论,若系统采用默认的 _id作为评论标识,我们知道,mongodb默认的 _id的类型为ObjectId,这是一个12byte组成的数据,分别由时间戳、机器标识码、mongoDB实例进程号以及随机序列组成。若是搭建了mongoDB的集群环境,在大量用户发布评论的情况下,生成的ObjectId是有可能相同的,这就导致了评论的唯一性被打破。
那么,我们也可以不使用mongodb默认的ObjectId,系统程序生成一个唯一标识设置为 _id ,例如雪花算法生成的id,不是吗?
是的,但是mongodb中集合是存在 _id 索引的,通过索引mongodb才能够快速的查询出指定的文档,在插入使,若使用自定义值的 _id, 那么将极大的影响mongodb文档的插入效率,特别是在集合中现存文档的数量极其庞大时尤为明显,所以上述做法会影响系统后期时的性能,也不建议使用。
退而求其次,不使用mongoDB默认的唯一标识不就好了,故cid诞生了。cid是由雪花算法生成的id,保证了评论的唯一性,而评论的_id 仍然使用默认类型,保证插入的高效性。而我们只需要另外建一个mongodb中cid索引就也能保证通过cid查询时的性能问题,这就是典型的以空间换时间的概念。
搭建项目的过程就不再赘述了。直接开始敲代码吧。
1、通过评论cid查询评论
2、通过文章id查询评论,顶级评论(评论文章的评论),按发布时间排序
3、通过文章id查询某一评论下的评论
3、通过用户id查询评论,按发布时间排序
4、根据id删除评论
5、评论点赞与取消点赞
6、发布评论
注意,评论我设置为没有修改评论功能,如果想改,就只能删除后进行重新发布评论。
对于遗漏的功能,通过上述的功能实现,基本也就能依葫芦画瓢了。
对于数据的参数校验问题,我使用的是hibernate validator,你们可以采用别的方法,这不属于本文内容。
采用restful来实现前后端分离。
我们只进行后端实现。
操作mongoDB的方式我采用了两种:
一种是使用MongoDBTemplate实现类似于需要用到列值增长等函数的操作;
一种是创建持久化接口CommentRepository 继承松日那个 data mongodb 提供的MongoDBRepository接口,进行方法拼接实现简单的条件查询。
这里我只给出操作mongoDB获取数据的方法,对于三层架构和spring boot的操作在代码中不体现。
直接从service层开始吧,表现层就不体现了。
创建Service接口
package cn.jankin.service; import cn.jankin.pojo.Comment; import java.util.List; import java.util.Map; /** * 评论业务接口 */ public interface CommentService { /** * 通过评论id获取评论数据 * @param id 评论id * @return */ Comment findById(String id); /** * 根据用户id查找用户所发布的评论,并按照发布时间排序 * @param userId * @param sortType * @return */ List<Comment> findByUserIdOrderByPublishdate(String userId,String sortType); /** * 查询所属指定文章的评论,不包含评论的评论,并按评论发布时间排序 * @param articleId * @param sortType * @return */ List<Comment> findByArticleIdOrderByPublishdate(String articleId,String sortType); /** * 通过评论id修改评论数据 * @param comment */ void updateById(Comment comment); /** * 通过评论id删除评论 * @param commentId */ void deleteById(String commentId); /** * 点赞评论功能实现 * @param commentId 被点赞评论id * @param isThumbup true : 点赞 ; false : 取消点赞 */ void thumbup(String commentId,Boolean isThumbup); /** * 发布评论 * @param comment */ void save(Comment comment); }
service
/** * 通过评论标识c_id查询评论数据 * @param id 评论id * @return */ @Override public Comment findById(String id) { Comment comment = commentRepositoty.findByCid(id); return comment; }
repository:采用方法条件拼接实现
/** * 通过评论c_id查询评论数据 * @param cid * @return */ Comment findByCid(String cid);
service
/** * 根据文章id查询评论,并按时间排序 * @param articleId * @param sortType * @return */ @Override public List<Comment> findByArticleIdOrderByPublishdate(String articleId, String sortType) { List<Comment> list = null; //判断排序规则 if(sortType.equals(SortType.ASC)){ list = commentRepositoty.findByArticleidOrderByPublishdateAsc(articleId); }else{ list = commentRepositoty.findByArticleidOrderByPublishdateDesc(articleId); } return list; }
在这里有一个sortType参数,是用来标识是升序排序还是降序排序。
package cn.jankin.entity; /** * 排序类型 */ public class SortType { public final static String ASC = "1"; //升序 public final static String DESC = "-1"; //降序 }
这样在我们需要改变前端排序参数时,直接该这个类就行,而不需要一个类一个类的改。设计原则:对修改封闭,对扩展开放。
repository
/** * 根据文章id查询评论,并根据发布时间正序排序 * @param articleId * @return */ List<Comment> findByArticleidOrderByPublishdateAsc(String articleId); /** * 根据文章id查询评论,并根据发布时间倒叙排序 * @param articleId * @return */ List<Comment> findByArticleidOrderByPublishdateDesc(String articleId);
因为与文章id查询做法相差不多,不再赘述,但是要考虑登录用户的问题,我这只是demo,没有实现用户登录功能。
service
/** * 删除指定评论id的评论文档 * @param commentId */ @Override public void deleteById(String commentId) { commentRepositoty.deleteByCid(commentId); }
repository
/** * 通过用户标识c_id删除评论 * @param cid */ void deleteByCid(String cid);
/** * 用户点赞与取消点赞评论根据评论id * @param commentId 被点赞评论id * @param isThumbup true : 点赞 ; false : 取消点赞 */ @Override public void thumbup(String commentId, Boolean isThumbup) { Query query = new Query(Criteria.where("cid").is(commentId)); //设置修改条件 Update update = new Update(); if(isThumbup){ //点赞操作 update.inc("thumbup",1); }else{ //取消点赞操作 update.inc("thumbup",-1); } mongoTemplate.updateFirst(query,update,"comment"); }
service
/** * 发布评论 * @param comment */ @Override public void save(Comment comment) { //生成id String id = idWorker.nextId()+""; comment.setCid(id); //设置发布时间 comment.setPublishdate(new Date()); //设置初始点赞数 comment.setThumbup(0); //保存 commentRepositoty.save(comment); }