学习目标:
- 理解MongoDB 的特点和体系结构
- 掌握常用的MongoDB命令
- 能够用java操作MongoDB
- 使用SpringDataMongoDB完成吐槽微服务的开发
1.MongoDB的简介
1.1 吐槽和评论数据特点分析
对于下面这样的数据,我们更适合使用MongoDB来实现数据的存储
- 数据量大
- 写入操作频繁
- 价值较低
1.2 什么是MongoDB
MongoDB是一种跨平台的,面向文档的数据库,是当前NoSql数据库产品中最热门的一种,它介于关系型数据库和非关系型数据库之间,是非关系型数据库当中功能最丰富,最像关系型数据库的产品。它支持的数据结构非常松散,是类似与JSON的BSON格式,因此可以存储比较复杂的数据类型。官网地址
http://www.mongodb.org/
1.3 MongoDB的特点
MongoDB最大的特点在与是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现列斯与关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。他是一个面向集合的,模式自由的文档型数据库。
- (1) 面向集合存储,易于存储对象类型的数据
- (2) 模式自由
- (3) 支持动态查询
- (4) 支持完全索引,包含内部对象
- (5) 支持复制和故障恢复
- (6) 使用高效的二进制数据存储,包括大型对象(如视频等)
- (7) 自动处理碎片,以支持云计算层次的扩展性
- (8) 支持python,php,java,c,c++javascript,perl的驱动程序,社区也提供了对Erlang及.net等平台的驱动程序
- (9) 文件存储格式为BSON(JSON的扩展)
1.4 MongoDB的体系结构
mongdb的逻辑结构是一种层次结构主要由
文档(document)
,集合(collection)
,数据库(database)
这三部分构成的。逻辑结构是面向用户的,用户使用MongoDB开发应用程序使用的就是逻辑结构。
- (1) MongoDB 的文档(document),相当于关系数据库中的一行记录。
- (2) 多个文档组成一个集合(collection),相当于关系数据库的表。
- (3) 多个集合(collection),逻辑上组织在一起,就是数据库(database)。
- (4) 一个 MongoDB 实例支持多个数据库(database)。
1.5 MongoDB与MySql数据库的逻辑结构概念的对比
MongoDB | MySql |
---|---|
数据库(databases) | 数据库(databases) |
集合(collections) | 表(table) |
文档(document) | 行(row) |
1.6 数据类型
- null:
用于表示空值或者不存在的字段,{“x”:null}
- 布尔型:
布尔类型有两个值true和false,{“x”:true}
- 数值:
shell默认使用64为浮点型数值。{“x”:3.14}或{“x”:3}。对于整型值,可以使用
NumberInt(4字节符号整数)或NumberLong(8字节符号整数),
{“x”:NumberInt(“3”)}{“x”:NumberLong(“3”)}
- 字符串:
UTF-8字符串都可以表示为字符串类型的数据,{“x”:“呵呵”}
- 日期:
日期被存储为自新纪元依赖经过的毫秒数,不存储时区,{“x”:new Date()}
- 正则表达式:
查询时,使用正则表达式作为限定条件,语法与JavaScript的正则表达式相
同,{“x”?[abc]/}
- 数组:
数据列表或数据集可以表示为数组,{“x”: [“a“,“b”,”c”]}
- 内嵌文档:
文档可以嵌套其他文档,被嵌套的文档作为值来处理,{“x”:{“y”:3 }}
- 对象Id:
对象id是一个12字节的字符串,是文档的唯一标识,{“x”: objectId() }
- 二进制数据:
二进制数据是一个任意字节的字符串。它不能直接在shell中使用。如果要
将非utf-字符保存到数据库中,二进制数据是唯一的方式。
- 代码
查询和文档中可以包括任何JavaScript代码,{“x”:function(){/…/}}
2. 走进MongoDB
2.1 MogoDB的安装与启动
2.1.1 Windows 系统的MongoDB安装
mongoDB的默认端口是27017 如果我们想改变默认的启动端口,可以通过–port来指定端口
2.1.1.1 下载安装
下载安装 mongodb-win32-x86_64-2008plusssl-3.2.10-signed.msi
双击按照提示下一步即可,默认安装在
C:\Program Files\MongoDB
中。
2.1.1.2 启动
- (1) 首先打开命令提示符,创建一个用于存放数据的目录
md d:\data
- (2)启动服务
mongod --dbpath=d:\data
- (3)登录
mongo
- (4)退出
exit
Docker环境下安装MongoDB
- (1) 检查docker 镜像在仓库中是否有
docker search mongo
- (2) 拉取官方镜像
docker pull mongo
- (3) 检查本地是否存在镜像了
docker images mongo
- (4) 启动docker中的镜像
docker run -di --name=snow_mongo -p 27017:27017 mongo
- (5) 启动docker中的镜像
docker run -di --name=snow_mongo -p 27017:27017 mongo
- (6) 检查启动的情况
docker ps | grep snow_mongo
- (7) 远程登录
这一步需要在宿主机安装mongo客户端,具体百度
mongo 114.67.156.73
2.1 MogoDB的常用命令
2.2.1 选择和创建数据库
- (1) 选择和创建数据库
use 数据库名称
这里创建 spit 数据库,如果数据库不存在则自动创建,如果存在则是切换数据库
> use spitdb
switched to db spitdb
2.2.2 插入与查询文档
每条文档会有一个叫_id的字段,这个相当于我们原来关系数据库中表的主
键,当你在插入文档记录时没有指定该字段,MongoDB会自动创建,其类型是ObjectID
类型。如果我们在插入文档记录时指定该字段也可以,其类型可以是ObjectID类型,也
可以是MongoDB支持的任意类型。
- (1): 插入文档的语法格式:
db.集合名称.insert(数据); 下面插入测试数据
db.spit.insert({content:"哇哈哈,是这个世界上最好的饮料",userid:"1011",nickname:"哇哈哈",visits:NumberInt(902)})
db.spit.insert({_id:"1",content:"小姐姐,小姐姐是世界上最好看的,没有之一",userid:"1012",nickname:"小黄",visits:NumberInt(2020)});
db.spit.insert({_id:"2",content:"上海市世界上我最不喜欢的城市.没有之一",userid:"1013",nickname:"下白",visits:NumberInt(1023)});
db.spit.insert({_id:"3",content:"喜欢深圳,因为喜欢的人在深圳",userid:"1013",nickname:"小花",visits:NumberInt(111)});
db.spit.insert({_id:"4",content:"坚持就是胜利",userid:"1014",nickname:"小青",visits:NumberInt(1223)});
- (2): 查询集合的语法格式:
db.集合名称.find(),默认是查询所有
开始测试
> db.spit.find()
{ "_id" : ObjectId("5c143012343bec2180f0e7b5"), "content" : "哇哈哈,是这个世界上最好的饮料", "userid" : "1011", "nickname" : "哇哈哈", "visits" : 902 }
{ "_id" : "1", "content" : "小姐姐,小姐姐是世界上最好看的,没有之一", "userid" : "1012", "nickname" : "小黄", "visits" : 2020 }
{ "_id" : "2", "content" : "上海市世界上我最不喜欢的城市.没有之一", "userid" : "1013", "nickname" : "下白", "visits" : 1023 }
{ "_id" : "3", "content" : "喜欢深圳,因为喜欢的人在深圳", "userid" : "1013", "nickname" : "小花", "visits" : 111 }
{ "_id" : "4", "content" : "坚持就是胜利", "userid" : "1014", "nickname" : "小青", "visits" : 1223 }
>
- (3): 按照条件查询
比如我想查询userid为1013的记录,只
要在find()中添加参数即可,参数也是json格式,如下:
> db.spit.find({userid:'1013'})
{ "_id" : "2", "content" : "上海市世界上我最不喜欢的城市.没有之一", "userid" : "1013", "nickname" : "下白", "visits" : 1023 }
{ "_id" : "3", "content" : "喜欢深圳,因为喜欢的人在深圳", "userid" : "1013", "nickname" : "小花", "visits" : 111 }
>
- (4): 只想查询一条记录
> db.spit.findOne({userid:'1013'})
{
"_id" : "2",
"content" : "上海市世界上我最不喜欢的城市.没有之一",
"userid" : "1013",
"nickname" : "下白",
"visits" : 1023
}
>
- (5): 返回指定行数的记录
> db.spit.find().limit(3)
{ "_id" : ObjectId("5c143012343bec2180f0e7b5"), "content" : "哇哈哈,是这个世界上最好的饮料", "userid" : "1011", "nickname" : "哇哈哈", "visits" : 902 }
{ "_id" : "1", "content" : "小姐姐,小姐姐是世界上最好看的,没有之一", "userid" : "1012", "nickname" : "小黄", "visits" : 2020 }
{ "_id" : "2", "content" : "上海市世界上我最不喜欢的城市.没有之一", "userid" : "1013", "nickname" : "下白", "visits" : 1023 }
>
2.2.3 修改文档
修改文档的语法结构:
db.集合名称.update(条件,修改后的数据)
示例代码
> db.spit.find({_id:"1"}) // 修改之前先查询一次
{ "_id" : "1", "content" : "小姐姐,小姐姐是世界上最好看的,没有之一", "userid" : "1012", "nickname" : "小黄", "visits" : 2020 }
>
> db.spit.update({_id:"1"},{visits:NumberInt(7000)}) // 你好
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
>
> db.spit.find({_id:"1"}) // 修改之后再查询一次,对比下记录
{ "_id" : "1", "visits" : 7000 }
>
执行了上面代码之后,我们惊奇的发现了.除了被修改值的visits还在,其他的都没了 !!!
为了解决这个问题,我们需要使用修改器KaTeX parse error: Expected '}', got 'EOF' at end of input: …实现 把你要修改的参数用 `{set: { 修改的参数和值 K:V }}`
示例代码: 将错误的nickname修改为正确的
> db.spit.find({"_id":"2"})
{ "_id" : "2", "content" : "上海是我最不喜欢的城市没有之一,小姐姐不在这里!", "userid" : "1013", "nickname" : "下白", "visits" : 1023 }
> db.spit.update({_id:"2"},{$set:{nickname:"小白"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.spit.find({"_id":"2"}) })
{ "_id" : "2", "content" : "上海是我最不喜欢的城市没有之一,小姐姐不在这里!", "userid" : "1013", "nickname" : "小白", "visits" : 1023 }
>
2.2.4 删除文档
删除文档的语法:
db.集合名称.remove({条件})
,如果条件为空db.spit.remove({})
将删除所有的请注意
示例代码: 删除_id 为4的文档
> db.spit.find({"_id":"4"})
{ "_id" : "4", "content" : "坚持就是胜利", "userid" : "1014", "nickname" : "小青", "visits" : 1223 }
> db.spit.remove({_id:"4"})
WriteResult({ "nRemoved" : 1 })
> db.spit.find({"_id":"4"})
>
>
2.2.5 统计条数
统计记录条件使用count()方法
- (1) 统计所有的
> db.spit.count()
4
>
- (2) 统计符合要求的,这里匹配包含 “小姐姐” 字样的
>
> db.spit.count({content:/小姐姐/})
1
> db.spit.find({})
{ "_id" : ObjectId("5c143012343bec2180f0e7b5"), "content" : "哇哈哈,是这个世界上最好的饮料", "userid" : "1011", "nickname" : "哇哈哈", "visits" : 902 }
{ "_id" : "1", "visits" : 7000 }
{ "_id" : "2", "content" : "上海是我最不喜欢的城市没有之一,小姐姐不在这里!", "userid" : "1013", "nickname" : "小白", "visits" : 1023 }
{ "_id" : "3", "content" : "喜欢深圳,因为喜欢的人在深圳", "userid" : "1013", "nickname" : "小花", "visits" : 111 }
2.2.6 模糊匹配
模糊匹配是通过正则表达式实现的 格式为:
/要匹配的字符串/
其实在2.2.5我已经用过了
- (1) 匹配某个字段内容包含 "小姐姐"字样的
> db.spit.find({content:/小姐姐/})
{ "_id" : "2", "content" : "上海是我最不喜欢的城市没有之一,小姐姐不在这里!", "userid" : "1013", "nickname" : "小白", "visits" : 1023 }
>
- (2) 匹配某个字段内容一 “上海” 开始的
> db.spit.find({content:/^上海/}))
{ "_id" : "2", "content" : "上海是我最不喜欢的城市没有之一,小姐姐不在这里!", "userid" : "1013", "nickname" : "小白", "visits" : 1023 }
>
- (3) 匹配某个字段内容以 “饮料” 结尾的
> db.spit.find({content:/饮料$/})
{ "_id" : ObjectId("5c143012343bec2180f0e7b5"), "content" : "哇哈哈,是这个世界上最好的饮料", "userid" : "1011", "nickname" : "哇哈哈", "visits" : 902 }
>
2.2.7 大于 小于 不等于
语法如下
db.集合名称.find({ "field" : { $gt: value }})
// 大于: field > valuedb.集合名称.find({ "field" : { $lt: value }})
// 小于: field < valuedb.集合名称.find({ "field" : { $gte: value }})
// 大于等于: field >= valuedb.集合名称.find({ "field" : { $lte: value }})
// 小于等于: field <= valuedb.集合名称.find({ "field" : { $ne: value }})
// 不等于: field != value
示例代码: 查询访客量大于 1000 的记录
> db.spit.find({visits:{$gt:1000}})
{ "_id" : "1", "visits" : 7000 }
{ "_id" : "2", "content" : "上海是我最不喜欢的城市没有之一,小姐姐不在这里!", "userid" : "1013", "nickname" : "小白", "visits" : 1023 }
2.2.8 包含与不包含
- 包含
包含使用$in操作符。示例代码如下:
> db.spit.find({_id:{$in:["2","3"]}})
{ "_id" : "2", "content" : "上海是我最不喜欢的城市没有之一,小姐姐不在这里!", "userid" : "1013", "nickname" : "小白", "visits" : 1023 }
{ "_id" : "3", "content" : "喜欢深圳,因为喜欢的人在深圳", "userid" : "1013", "nickname" : "小花", "visits" : 111 }
>
- 不包含
不包含使用$nin操作符
> db.spit.find({_id:{$nin:["2","3"]}})
{ "_id" : "1", "visits" : 7000 }
{ "_id" : ObjectId("5c143012343bec2180f0e7b5"), "content" : "哇哈哈,是这个世界上最好的饮料", "userid" : "1011", "nickname" : "哇哈哈", "visits" : 902 }
>
2.2.9 条件连接
- and操作
$and:[ { },{ },{ } ]
我们如果需要查询同时满足两个以上条件,需要使用$and操作符将条件进行关联。
示例代码: 查询访问量 visits大于等于1000 并且小于2000的文档
> db.spit.find({$and:[ {visits:{$gte:1000}} ,{visits:{$lt:2000} }]})
{ "_id" : "2", "content" : "上海是我最不喜欢的城市没有之一,小姐姐不在这里!", "userid" : "1013", "nickname" : "小白", "visits" : 1023 }
>
// 生产中不能这么写,我只是为了试试语法是不是能正常解析这种而已
> db.spit.find({$and:[ {_id:{$in:["2","3"]}},{_id:"2"}]})
{ "_id" : "2", "content" : "上海是我最不喜欢的城市没有之一,小姐姐不在这里!", "userid" : "1013", "nickname" : "小白", "visits" : 1023 }
>
- or操作
$or:[ { },{ },{ } ]
示例代码: 查询集合中userid为1013,或者浏览量小于2000的文档记录
> db.spit.find({$or:[ {userid:"1013"} ,{visits:{$lt:2000} }]})
{ "_id" : ObjectId("5c143012343bec2180f0e7b5"), "content" : "哇哈哈,是这个世界上最好的饮料", "userid" : "1011", "nickname" : "哇哈哈", "visits" : 902 }
{ "_id" : "2", "content" : "上海是我最不喜欢的城市没有之一,小姐姐不在这里!", "userid" : "1013", "nickname" : "小白", "visits" : 1023 }
{ "_id" : "3", "content" : "喜欢深圳,因为喜欢的人在深圳", "userid" : "1013", "nickname" : "小花", "visits" : 111 }
>
2.2.10 列值增长
如果我们想实现对某列值在原有值的基础上进行增加或减少,可以使用$inc运算符来实现
示例代码: 给_ID为2的用户访问量增加1
> db.spit.find({_id:"2"}) // 查询修改之前
{ "_id" : "2", "content" : "上海是我最不喜欢的城市没有之一,小姐姐不在这里!", "userid" : "1013", "nickname" : "小白", "visits" : 1023 }
>
> db.spit.update({_id:"2"},{$inc:{visits:NumberInt(1)}} )
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.spit.find({_id:"2"}) // 查询修改之后
{ "_id" : "2", "content" : "上海是我最不喜欢的城市没有之一,小姐姐不在这里!", "userid" : "1013", "nickname" : "小白", "visits" : 1024 }
>
3. Java操作MongoDB
3.1 MongoDB 驱动
mongodb-driver是mongo官方推出的java连接mongoDB的驱动包,相当于JDBC驱动。
3.1.1 测试MONGODB, JAVA API准备工作
- (1) 创建Maven公衡工程 mongoDemo
- (2) 引入依赖
<!--mongodb 驱动类-->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<version>3.6.3</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.12</version>
</dependency>
- (3) 新建一个测试类
MongoDemo
代码如下:
package com.snow.demo.mongo;
import com.mongodb.BasicDBObject;
import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@Slf4j
public class MongoDemo {
private MongoClient client = null;
private MongoDatabase spitdb = null;
private MongoCollection<Document> spit = null;
private static final String MONGO_HOST = "114.67.156.73";
private static final String MONGO_DATABASE_NAME = "spitdb";
private static final String MONGO_COLLECTION_NAME = "spit";
@Before
public void before(){
log.info("初始化资源 > > > 开始");
client=new MongoClient(MONGO_HOST);//创建连接
spitdb = client.getDatabase(MONGO_DATABASE_NAME);//打开数据库
spit = spitdb.getCollection(MONGO_COLLECTION_NAME);//
log.info("初始化资源 > > > 结束");
}
@After
public void after(){
log.info("关闭资源 > > > ");
if (client != null){
client.close();//关闭连接
}
}
/**
* 打印文档信息
* @param documents
*/
private void showDocuments(FindIterable<Document> documents){
log.info("打印集合信息----开始 >>>>> ");
for(Document document:documents){ //
log.info("内容:【{}】,用户ID:【{}】,浏览量:【{}】",document.getString("content"),document.getString("userid"),document.getInteger("visits"));
}
log.info("打印集合信息----结束 >>>>> ");
}
}
3.1.2 查询全部记录
//查询所有
@Test
public void testFind(){
// 获取集合
FindIterable<Document> documents = spit.find();//查询记录获取文档集合
showDocuments(documents);
}
3.1.3 条件查询
查询所有和按照条件查询其实就是参数不同了而已,参数需要使用到
BasicDBObject
对象
BasicDBObject对象:表示一个具体的记录,BasicDBObject实现了DBObject,是keyvalue的数据结构,用起来和HashMap是基本一致的。
- (1) 查询userId为1013的
// 测试查询所有userId为1013的
@Test
public void testFindByUserID(){
// 获取集合
BasicDBObject paramter = new BasicDBObject("userid","1013");
FindIterable<Document> documents = spit.find(paramter);//查询记录获取文档集合
showDocuments(documents);
}
- (2) 查询浏览量大于1000的记录
// 查询访问量大于1000的
@Test
public void testFindByVisitsGT(){
// 获取集合
BasicDBObject paramter = new BasicDBObject("visits", new BasicDBObject("$gt",1000));
FindIterable<Document> documents = spit.find(paramter);//查询记录获取文档集合
showDocuments(documents);
}
3.1.4 插入数据
- (1) : 执行插入语句
// 查询访问量大于1000的
@Test
public void testInsertToMongo(){
// 准备要插入的参数
Map<String,Object> insertParamMap=new HashMap();
insertParamMap.put("content","想小姐姐了.不知道小姐姐有没有每天好好吃饭,每天好好睡觉 ... ");
insertParamMap.put("userid","1024");
insertParamMap.put("visits",1024);
insertParamMap.put("publishtime",new Date());
// 构建文档对象
Document document = new Document(insertParamMap);
spit.insertOne(document);
}
- (2) : 插入之后去查询下,看看有没有了
> db.spit.find({userid:"1024"})
{ "_id" : ObjectId("5c144faf04c7b30d1ce528f9"), "visits" : 1024, "publishtime" : ISODate("2018-12-15T00:49:51.143Z"), "userid" : "1024", "content" : "想小姐姐了.不知道小姐姐有没有每天好好吃饭,每天好好睡觉 ... " }
>
3.2 SpringDataMongoDB
SpringData家族成员之一,用于操作MongoDb的持久层框架,封装了底层的mongodbdriver。
官网主页:https://projects.spring.io/spring-data-mongodb/
4: 吐槽微服务
4.1 表结构分析
吐槽表 spit
字段名称 | 字段含义 | 字段类型 | 备注 |
---|---|---|---|
_id | ID | 文本 | |
content | 吐槽内容 | 文本 | |
publishtime | 发布日期 | 日期 | |
userid | 发布人ID | 文本 | |
nickname | 发布人昵称 | 文本 | |
visits | 浏览量 | 整型 | |
thumbup | 点赞数 | 整型 | |
share | 分享数 | 整型 | |
comment | 回复数 | 整型 | |
state | 是否可见 | 文本 | |
parentid | 上级ID | 文本 |
4.2 需求分析
采用SpringDataMongoDB框架实现吐槽微服务的持久层
要实现如下功能:
-
- 基本增删改查API
-
- 根据上级ID查询吐槽列表
-
- 吐槽点赞
-
- 发布吐槽
4.2 代码编写
4.2.1 模块搭建
- (1)搭建子模块 tensquare-spit (拷贝mongo-demo,改名和修改pom文件即可)
- (2)pom.xml引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>com.tensquare</groupId>
<artifactId>tensquare-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- (3) 创建application.yml
server:
port: 9006
spring:
application:
name: tensquare‐spit #指定服务名
data:
mongodb:
host: 114.67.156.73
database: spitdb
- (4)创建启动类
package com.tensquare.spit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import util.IdWorker;
@SpringBootApplication
public class SpitApplication {
public static void main(String[] args) {
SpringApplication.run(SpitApplication.class, args);
}
@Bean
public IdWorker idWorkker(){
return new IdWorker(1, 1);
}
}
4.2.2 基本增删改查API实现
- (1) 准备工作
pojo
package com.tensquare.spit.pojo;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.data.annotation.Id;
import java.io.Serializable;
import java.util.Date;
@Data
@NoArgsConstructor
@Accessors(chain = true)
@Builder
public class Spit implements Serializable {
@Id
private String _id;
private String content;
private Date publishtime;
private String userid;
private String nickname;
private Integer visits;
private Integer thumbup;
private Integer share;
private Integer comment;
private String state;
private String parentid;
}
- (2) 创建数据访问接口
dao
创建dao包,包下创建接口
SpitDao
package com.tensquare.spit.dao;
import com.tensquare.spit.pojo.Spit;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* 吐槽数据访问层
*/
public interface SpitDao extends MongoRepository<Spit,String> {
}
- (3) 创建业务逻辑类
service
package com.tensquare.spit.service;
import com.tensquare.spit.dao.SpitDao;
import com.tensquare.spit.pojo.Spit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import util.IdWorker;
import java.util.List;
@Service
public class SpitService {
@Autowired
private SpitDao spitDao;
@Autowired
private IdWorker idWorker;
/**
* 查询所有记录
* @return
*/
public List<Spit> findAll(){
return spitDao.findAll();
}
/**
* 根据逐渐Id查询
* @param id
* @return
*/
public Spit findById(String id){
Spit spit = spitDao.findById(id).get();
return spit;
}
/**
* 增加
* @param spit
*/
public void add(Spit spit){
spit.set_id(String.valueOf(idWorker.nextId()));
spitDao.save(spit);
}
/**
* 修改
* @param spit
*/
public void update(Spit spit){
spitDao.save(spit);
}
/**
* 根据ID删除
* @param id
*/
public void deleteById(String id){
spitDao.deleteById(id);
}
}
- (4) 访问控制器实现
controller
package com.tensquare.spit.controller;
import com.tensquare.spit.pojo.Spit;
import com.tensquare.spit.service.SpitService;
import entity.Result;
import entity.StatusCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@CrossOrigin
@RequestMapping("/spit")
public class SpitController {
@Autowired
private SpitService spitService;
//查询所有
@GetMapping
public Result findAll(){
return new Result(true, StatusCode.OK,"查询成功",spitService.findAll());
}
//根据ID查询
@GetMapping("/{id}")
public Result findOne(@PathVariable String id){
return new Result(true, StatusCode.OK,"查询成功",spitService.findById(id));
}
//增加
@PostMapping
public Result add(@RequestBody Spit spit ){
spitService.add(spit);
return new Result(true,StatusCode.OK,"增加成功");
}
//根据ID修改
@PutMapping("/{id}")
public Result update(@RequestBody Spit spit,@PathVariable String id )
{
spit.set_id(id);
spitService.update(spit);
return new Result(true,StatusCode.OK,"修改成功");
}
//根据ID删除
@DeleteMapping("/{id}")
public Result deleteById(@PathVariable String id ){
spitService.deleteById(id);
return new Result(true,StatusCode.OK,"删除成功");
}
}
- (5) 调试下
测试findAll即可,这里直接采用idea控制台命令
curl
操作. 如果你的控制台中文是乱码了.请尝试下面操作
- 切换控制台编码格式为UTF-8
chcp 65001
- 切换控制台编码格式为简体中文
chcp 936
- 开始执行访问
curl http://localhost:9006/spit
C:\Users\snow\Desktop\tensquare-parent>curl http://localhost:9006/spit
{"flag":true,"code":20000,"message":"查询成功","data":[{"_id":"5c143012343bec2180f0e7b5","content":"哇哈哈,是这个世界上最好的饮料","publishtime":null,"userid":"1011","nickname":"哇哈哈","visits":902,"
thumbup":null,"share":null,"comment":null,"state":null,"parentid":null},{"_id":"1","content":null,"publishtime":null,"userid":null,"nickname":null,"visits":7000,"thumbup":null,"share":null,"comment":n
ull,"state":null,"parentid":null},{"_id":"2","content":"上海是我最不喜欢的城市没有之一,小姐姐不在这里!","publishtime":null,"userid":"1013","nickname":"小白","visits":1024,"thumbup":null,"share":null,"
comment":null,"state":null,"parentid":null},{"_id":"3","content":"喜欢深圳,因为喜欢的人在深圳","publishtime":null,"userid":"1013","nickname":"小花","visits":111,"thumbup":null,"share":null,"comment":n
ull,"state":null,"parentid":null},{"_id":"5c144faf04c7b30d1ce528f9","content":"想小姐姐了.不知道小姐姐有没有每天好好吃饭,每天好好睡觉 ... ","publishtime":"2018-12-15T00:49:51.143+0000","userid":"1024"
,"nickname":null,"visits":1024,"thumbup":null,"share":null,"comment":null,"state":null,"parentid":null}]}
C:\Users\snow\Desktop\tensquare-parent>
4.2.3 根据上级ID查询吐槽列表
- (1) SpitDao新增方法定义
/**
* 根据上级ID查询吐槽列表(分页)
* @param parentid
* @param pageable
* @return
*/
Page<Spit> findByParentid(String parentid, Pageable pageable);
- (2) SpitService新增方法
/**
* 根据上级ID查询吐槽列表
* @param parentId
* @param page
* @param size
* @return
*/
public Page<Spit> findByParentId(String parentId, int page, int size){
PageRequest pageRequest = PageRequest.of(page-1, size);
return spitDao.findByParentid(parentId, pageRequest);
}
- (3) SpitController新增方法
/**
* 根据上级ID查询吐槽分页数据
*
* @param page
* @param size
* @return
*/
@GetMapping("/comment/{parentId}/{page}/{size}")
public Result findByParentid(@PathVariable String parentId,
@PathVariable int page,
@PathVariable int size) {
Page<Spit> pageList = spitService.findByParentId(parentId, page, size);
return new Result(true, StatusCode.OK, "查询成功", new
PageResult<Spit>(pageList.getTotalElements(), pageList.getContent()));
}
4.2.4 吐槽点赞
- (1) SpitService新增方法
/**
* 点赞
* @param id
*/
public void updateThumbup2(String id){
Spit spit = spitDao.findById(id).get();
spit.setThumbup(spit.getThumbup()+1);
spitDao.save(spit);
}
- (2) SpitController新增方法
@PutMapping("/thumbup/{spitId}")
public Result thumbup(@PathVariable String spitId){
spitService.updateThumbup(spitId);
return new Result(true,StatusCode.OK,"点赞成功");
}
- (3) SpitService中
updateThumbup
方法优化
以上方法虽然实现起来比较简单,但是执行效率并不高,因为我只需要将点赞数加1就可
以了,没必要查询出所有字段修改后再更新所有字段
@Autowired //引入mongoTemplate 的 api
private MongoTemplate mongoTemplate;
/**
* 点赞
* @param id
*/
public void updateThumbup(String id){
//使用原生mongoTemplate的api去操作,效率更高
Query query=new Query();
query.addCriteria(Criteria.where("_id").is(id));
Update update=new Update();
update.inc("thumbup",1);
mongoTemplate.updateFirst(query,update,"spit");
}
4.2.5 控制不能重复点赞
在点赞的时候我们可以通过redis来控制用户不能重复点赞
- (1) 引入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- (2) 修改
application.yml
的配置文件
redis:
host: 127.0.0.1
password: hbtrustRedis9527SC
- (3) 在
SpitController
中增加 RedisTemplate 的引用
@Autowired
private RedisTemplate redisTemplate;
- (4) 在
SpitController
中增加 RedisTemplate 的引用
@Autowired
private RedisTemplate redisTemplate;
- (5) 在
SpitController
类中修改点赞thumbup
方法
@PutMapping("/thumbup/{spitId}")
public Result thumbup(@PathVariable String spitId){
//判断当前用户是否已经点赞,但是现在我们没有做认证,暂时先把userid写死
String userid = "111";
//判断当前用户是否已经点赞
if(redisTemplate.opsForValue().get("thumbup_"+userid)!=null){
return new Result(false, StatusCode.REP_ERROR, "不能重复点赞");
}
spitService.updateThumbup(spitId);
redisTemplate.opsForValue().set("thumbup_"+userid, 1);
return new Result(true, StatusCode.OK, "点赞成功");
}
4.2.6 发布吐槽
- (1) 修改
SpitService
中增加add
方法
/**
* 增加
* @param spit
*/
public void add(Spit spit){
spit.set_id(String.valueOf(idWorker.nextId()));
spit.setPublishtime(new Date());//发布日期
spit.setVisits(0);//浏览量
spit.setShare(0);//分享数
spit.setThumbup(0);//点赞数
spit.setComment(0);//回复数
spit.setState("1");//状态
//如果当前添加的吐槽,有父节点,那么父节点的吐槽回复数要加一
if(StringUtils.hasText(spit.getParentid())){
Query query = new Query();
query.addCriteria(Criteria.where("_id").is(spit.getParentid()));
Update update = new Update();
update.inc("comment", 1);
mongoTemplate.updateFirst(query, update, "spit");
}
spitDao.save(spit);
}
4.2.7 增加浏览量和分享数
- (1)
SpitService
中增加一个公共的方法,用来自增加入一条记录中某个记录的值
/**
* 通过id修改一条数据中的某个key
* @param id 指定记录的id
* @param key 需要修改的key
* @param collectionName 指定集合名称
*/
private void incKeyById(String id,String key,String collectionName){
Query query = new Query();
query.addCriteria(Criteria.where("_id").is(id));
Update update = new Update();
update.inc(key, 1);
mongoTemplate.updateFirst(query, update, collectionName);
}
- (2) 修改
SpitService
中增加add
方法
/**
* 增加
* @param spit
*/
public void add(Spit spit){
spit.set_id(String.valueOf(idWorker.nextId()));
spit.setPublishtime(new Date());//发布日期
spit.setVisits(0);//浏览量
spit.setShare(0);//分享数
spit.setThumbup(0);//点赞数
spit.setComment(0);//回复数
spit.setState("1");//状态
//如果当前添加的吐槽,有父节点,那么父节点的吐槽回复数要加一
if(StringUtils.hasText(spit.getParentid())){
incKeyById(spit.getParentid(),"comment","spit");
}
// 增加分享数
if(spit.getShare() != null && spit.getShare() > 0){
incKeyById(spit.get_id(),"share","spit");
}
// 增加浏览量
if(spit.getVisits() != null && spit.getVisits() > 0){
incKeyById(spit.get_id(),"visits","spit");
}
spitDao.save(spit);
}
5.文章评论功能开发
5.1 表结构分析
专栏文章评论 comment
字段名称 | 字段含义 | 字段类型 | 备注 |
---|---|---|---|
_id | ID | 文本 | |
articleid | 文章ID | 文本 | |
content | 评论内容 | 文本 | |
userid | 评论人ID | 文本 | |
parentid | 评论ID | 文本 | 如果为0表示文章的顶级评论 |
publishdate | 评论日期 | 日期 |
5.2 代码实现
5.2.1 新增评论
- (1) 修改
tensquare-article
工程的pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
- (2) 修改
application.yml
,在spring
节点下新增配置
data:
mongodb:
host: 114.67.156.73
database: spitdb
- (3) 创建实体类
package com.tensquare.article.pojo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.annotation.Id;
import java.io.Serializable;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@Builder
@Slf4j
public class Comment implements Serializable {
@Id
private String _id;
private String articleid;
private String content;
private String userid;
private String parentid;
private Date publishdate;
}
- (4) 创建数据访问接口
package com.tensquare.article.dao;
import com.tensquare.article.pojo.Comment;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* 评论DAO
*/
public interface CommentDao extends MongoRepository<Comment,String> {
}
- (5) 创建业务逻辑类
package com.tensquare.article.service;
import com.tensquare.article.dao.CommentDao;
import com.tensquare.article.pojo.Comment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import util.IdWorker;
@Service
public class CommentService {
@Autowired
private CommentDao commentDao;
@Autowired
private IdWorker idWorker;
/**
* 增加评论
* @param comment
*/
public void add(Comment comment){
comment.set_id( String.valueOf(idWorker.nextId()));
commentDao.save(comment);
}
}
- (6) 创建控制器类
package com.tensquare.article.controller;
import com.tensquare.article.pojo.Comment;
import com.tensquare.article.service.CommentService;
import entity.Result;
import entity.StatusCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@CrossOrigin
@RequestMapping("/comment")
public class CommentController {
@Autowired
private CommentService commentService;
@PostMapping
public Result save(@RequestBody Comment comment){
commentService.add(comment);
return new Result(true, StatusCode.OK, "提交成功 ");
}
}
5.2.2 根据文章ID查询评论列表
- (1)
CommenDao
中增加方法
/**
* 根据文章ID查询评论列表
* @param articleid
* @return
*/
public List<Comment> findByArticleid(String articleid);
- (2)
CommenService
中增加方法
/**
* 根据文章ID查询评论列表
* @param articleid 文章id
* @return
*/
public List<Comment> findByArticleid(String articleid){
return commentDao.findByArticleid(articleid);
}
- (3)
CommenController
中增加方法
@GetMapping("/article/{articleid}")
public Result findByArticleid(@PathVariable String articleid){
return new Result(true, StatusCode.OK, "查询成功",
commentService.findByArticleid(articleid));
}
5.2.3 删除评论
- (1)
CommenService
中增加方法
/**
* 根据ID删除评论
* @param id
*/
public void deleteById(String id){
commentDao.deleteById(id);
}
- (2)
CommenController
中增加方法
@DeleteMapping("/{id}")
public Result deleteById(@PathVariable String id) {
commentService.deleteById(id);
return new Result(true, StatusCode.OK, "删除成功");
}