需要实现以下功能:
1)基本增删改查API
2)根据文章id查询评论
3)评论点赞
存放文章评论的数据存放到MongoDB中,数据结构参考如下:
数据库: articledb
专栏文章评论: comment
字段名称 | 字段含义 | 字段类型 | 备注 |
---|---|---|---|
_id | ID | ObjectId或String | Mongo的主键的字段 |
articleid | 文章ID | String | |
content | 评论内容 | String | |
userid | 评论人ID | String | |
nickname | 评论人昵称 | String | |
createdatetime | 评论的日期时间 | Date | |
likenum | 点赞数 | Int32 | |
replynum | 回复数 | Int32 | |
state | 状态 | String | 0:不可见;1:可见; |
parentid | 上级ID | String | 如果为0表示文章的顶级评论 |
文章示例参考:早晨空腹喝水,是对还是错?https://www.toutiao.com/a6721476546088927748/.
准备数据
try { db.comment.insertMany([ {"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"},{"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬天喝温开水","userid":"1005","nickname":"伊人憔悴","createdatetime":new Date("2019-08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"},{"_id":"3","articleid":"100001","content":"我一直喝凉开水,冬天夏天都喝。","userid":"1004","nickname":"杰克船长","createdatetime":new Date("2019-08-06T01:05:06.321Z"),"likenum":NumberInt(666),"state":"1"},{"_id":"4","articleid":"100001","content":"专家说不能空腹吃饭,影响健康。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T08:18:35.288Z"),"likenum":NumberInt(2000),"state":"1"},{"_id":"5","articleid":"100001","content":"研究表明,刚烧开的水千万不能喝,因为烫嘴。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"}]); } catch (e) { print (e); }
mongodb-driver是mongo官方推出的java连接mongoDB的驱动包,相当于JDBC驱动。我们通过一个入门的案例来了解mongodb-driver
的基本使用。
官方驱动说明和下载:http://mongodb.github.io/mongo-java-driver/
官方驱动示例文档:http://mongodb.github.io/mongo-java-driver/3.8/driver/getting-started/quick-start/
SpringData家族成员之一,用于操作MongoDB的持久层框架,封装了底层的mongodb-driver。
官网主页: https://projects.spring.io/spring-data-mongodb/
我们十次方项目的吐槽微服务就采用SpringDataMongoDB框架。
(1)搭建项目工程article,pom.xml引入依赖:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.itcast</groupId> <artifactId>article</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> </dependencies> </project>
(2)配置application.yml
spring: #数据源配置 data: mongodb: # 主机地址 host: 120.79.157.129 # 数据库 database: articledb # 默认端口是27017 port: 27017 #也可以使用uri连接 #uri: mongodb://120.79.157.129:27017/articledb
(3)创建启动类
cn.itcast.article.ArticleApplication
package cn.itcast.article; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ArticleApplication { public static void main(String[] args) { SpringApplication.run(ArticleApplication.class, args); } }
(4)启动项目,看是否能正常启动,控制台没有错误。
创建实体类 创建包cn.itcast.article,包下建包po用于存放实体类,创建实体类
package cn.itcast.article.po; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.index.CompoundIndex; import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; import java.io.Serializable; import java.time.LocalDateTime; import java.util.Date; /** * 文章评论实体类 */ //把一个java类声明为mongodb的文档,可以通过collection参数指定这个类对应的文档。 //@Document(collection="mongodb 对应 collection 名") // 若未加 @Document ,该 bean save 到 mongo 的 comment collection // 若添加 @Document ,则 save 到 comment collection @Document(collection="comment")//可以省略,如果省略,则默认使用类名小写映射集合 //复合索引 @CompoundIndex( def = "{'userid': 1, 'nickname': -1}") public class Comment implements Serializable { //主键标识,该属性的值会自动对应mongodb的主键字段"_id",如果该属性名就叫“id”,则该注解可以省略,否则必须写 // @Id private String id;//主键 //该属性对应mongodb的字段的名字,如果一致,则无需该注解 @Field("content") private String content;//吐槽内容 private Date publishtime;//发布日期 //添加了一个单字段的索引 @Indexed private String userid;//发布人ID private String nickname;//昵称 private LocalDateTime createdatetime;//评论的日期时间 private Integer likenum;//点赞数 private Integer replynum;//回复数 private String state;//状态 private String parentid;//上级ID private String articleid; //getter and setter..... public String getId() { return id; } public void setId(String id) { this.id = id; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Date getPublishtime() { return publishtime; } public void setPublishtime(Date publishtime) { this.publishtime = publishtime; } public String getUserid() { return userid; } public void setUserid(String userid) { this.userid = userid; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public LocalDateTime getCreatedatetime() { return createdatetime; } public void setCreatedatetime(LocalDateTime createdatetime) { this.createdatetime = createdatetime; } public Integer getLikenum() { return likenum; } public void setLikenum(Integer likenum) { this.likenum = likenum; } public Integer getReplynum() { return replynum; } public void setReplynum(Integer replynum) { this.replynum = replynum; } public String getState() { return state; } public void setState(String state) { this.state = state; } public String getParentid() { return parentid; } public void setParentid(String parentid) { this.parentid = parentid; } public String getArticleid() { return articleid; } public void setArticleid(String articleid) { this.articleid = articleid; } @Override public String toString() { return "Comment{" + "id='" + id + '\'' + ", content='" + content + '\'' + ", publishtime=" + publishtime + ", userid='" + userid + '\'' + ", nickname='" + nickname + '\'' + ", createdatetime=" + createdatetime + ", likenum=" + likenum + ", replynum=" + replynum + ", state='" + state + '\'' + ", parentid='" + parentid + '\'' + ", articleid='" + articleid + '\'' + '}'; } }
说明:
索引可以大大提升查询效率,一般在查询字段上添加索引,索引的添加可以通过Mongo的命令来添加,也可以在Java的实体类中通过注解添加。
1)单字段索引注解@Indexed
org.springframework.data.mongodb.core.index.Indexed.class
声明该字段需要索引,建索引可以大大的提高查询效率。
Mongo命令参考:
db.comment.createIndex({"userid":1})
2)复合索引注解@CompoundIndex
org.springframework.data.mongodb.core.index.CompoundIndex.class
复合索引的声明,建复合索引可以有效地提高多字段的查询效率。
Mongo命令参考:
db.comment.createIndex({"userid":1,"nickname":-1})
(1)创建数据访问接口 cn.itcast.article包下创建dao包,包下创建接口
cn.itcast.article.dao.CommentRepository
package cn.itcast.article.dao; import cn.itcast.article.po.Comment; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; public interface CommentRepository extends MongoRepository<Comment,String > { }
(2)创建业务逻辑类 cn.itcast.article包下创建service包,包下创建类
cn.itcast.article.service.CommentService
package cn.itcast.article.service; import cn.itcast.article.dao.CommentRepository; import cn.itcast.article.po.Comment; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Service; import java.util.List; @Service public class CommentService { @Autowired private CommentRepository commentRepository; @Autowired private MongoTemplate mongoTemplate; /** * 保存一个评论 * @param comment */ public void saveComment(Comment comment){ //如果需要自定义主键,可以在这里指定主键;如果不指定主键,MongoDB会自动生成主键 //设置一些默认初始值。。。 //调用dao commentRepository.save(comment); } /** * 更新评论 * @param comment */ public void updateComment(Comment comment){ //调用dao commentRepository.save(comment); } /** * 根据id删除评论 * @param id */ public void deleteCommentById(String id){ //调用dao commentRepository.deleteById(id); } /** * 查询所有评论 * @return */ public List<Comment> findCommentList(){ //调用dao return commentRepository.findAll(); } /** * 根据id查询评论 * @param id * @return */ public Comment findCommentById(String id){ //调用dao return commentRepository.findById(id).get(); } }
(3)新建Junit测试类,测试保存和查询所有:
cn.itcast.article.service.CommentServiceTest
package cn.itcast.article.service; import cn.itcast.article.po.Comment; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.test.context.junit4.SpringRunner; import java.time.LocalDateTime; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class CommentServiceTest { @Autowired private CommentService commentService; @Test public void testFindCommentList() { List<Comment> commentList = commentService.findCommentList(); System.out.println(commentList); } @Test public void testFindCommentById() { Comment commentById = commentService.findCommentById("1"); System.out.println(commentById); } @Test public void testSaveComment(){ Comment comment=new Comment(); comment.setArticleid("100000"); comment.setContent("测试添加的数据"); comment.setCreatedatetime(LocalDateTime.now()); comment.setUserid("1003"); comment.setNickname("凯撒大帝"); comment.setState("1"); comment.setLikenum(0); comment.setReplynum(0); commentService.saveComment(comment); } }
添加结果:
(1)CommentRepository新增方法定义
//根据父id,查询子评论的分页列表 Page<Comment> findByParentid(String parentid,Pageable pageable);
(2)CommentService新增方法
/** * 根据父id查询分页列表 * @param parentid * @param page * @param size * @return */ public Page<Comment> findCommentListByParentid(String parentid,int page,int size) { return commentRepository.findByParentid(parentid,PageRequest.of(page-1,size)); }
(3)junit测试用例:
cn.itcast.article.service.CommentServiceTest
/** * 测试根据父id查询子评论的分页列表 */ @Test public void testFindCommentListByParentid() { Page<Comment> page = commentService.findCommentListByParentid("3", 1, 2); System.out.println(page.getTotalElements()); System.out.println(page.getContent()); }
(4)测试
使用compass快速插入一条测试数据,数据的内容是对3号评论内容进行评论。
执行测试,结果:
我们看一下以下点赞的临时示例代码: CommentService 新增updateThumbup方法
/** * 点赞-效率低 * @param id */ public void updateCommentThumbupToIncrementingOld(String id){ Comment comment = CommentRepository.findById(id).get(); comment.setLikenum(comment.getLikenum()+1); CommentRepository.save(comment); }
以上方法虽然实现起来比较简单,但是执行效率并不高,因为我只需要将点赞数加1就可以了,没必要查询出所有字段修改后再更新所有字
段。(蝴蝶效应)
我们可以使用MongoTemplate类来实现对某列的操作。
(1)修改CommentService
//注入MongoTemplate @Autowired private MongoTemplate mongoTemplate; /** * 点赞数+1 * @param id */ public void updateCommentLikenum(String id){ // 查询条件 Query query = Query.query(Criteria.where("_id").is(id)); // 更新条件 Update update = new Update(); //局部更新,相当于$set // update.set(key,value) //递增$inc update.inc("likenum"); //参数1:查询对象 //参数2:更新对象 //参数3:集合的名字或实体类的类型Comment.class mongoTemplate.updateFirst(query,update,Comment.class); }
(2)测试用例:
cn.itcast.article.service.CommentServiceTest
/** * 点赞数+1 */ @Test public void testUpdateCommentLikenum() { //对3号文档的点赞数+1,原点赞数666 commentService.updateCommentLikenum("3"); }
执行测试用例后,发现点赞数+1了: