1.MongoDB相关概念
业务应用场景
MongoDB简介
BSON=二进制的JSON
数据类型
MongDB的特点
2.单机部署
windows上的安装启动
windows版本的直接去官网下载即可,这里的安装运行我试了一次没有成功。干脆不用了,反正以后也不会在windows系统上用的这个
linux上的安装启动
这里使用docker容器的方式安装使用。
直接docker拉取镜像运行
docker pull mongo
新建数据持久化目录
mkdir -p /docker_volume/mongodb/data
创建并运行容器
docker run -itd --name mongo -v /docker_volume/mongodb/data:/data/db -p 27017:27017 mongo:latest
进入mongodb的admin数据库
docker exec -it mongo mongo admin
这条好像是高版本用的
docker exec -it mongo mongosh admin
创建用户
新版本的默认有一个root用户。
db.createUser({ user:'root',pwd:'123456',roles:[ { role:'userAdminAnyDatabase', db: 'admin'},'readWriteAnyDatabase']});
设置密码
db.auth('root', '123456')
shell连接和compass使用
在可视化界面直接连接即可.
3.基本常用命令
数据库操作
创建数据库
不存在的数据库会直接在内存里创建,但是没有在磁盘创建。
use DATABASE_NAME
查看所有数据库
show dbs
show databases
MongoDB 中默认的数据库为 test,如果你没有创建新的数据库,集合将存放在 test 数据库中。
注意: 在 MongoDB 中,集合只有在内容插入后才会创建! 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。
删除数据库
删除当前数据库
db.dropDatabase()
使用db可以查看当前所在数据库
集合操作
删除集合
在某一个数据库下通过指定集合名字的方式删除
db.collectionName.drop()
创建集合
db.createCollection(name, options)
参数说明:
- name: 要创建的集合名称
- options: 可选参数, 指定有关内存大小及索引的选项
options 可以是如下参数:
字段 | 类型 | 描述 |
---|---|---|
capped | 布尔 | (可选)如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。 当该值为 true 时,必须指定 size 参数。 |
autoIndexId | 布尔 | 3.2 之后不再支持该参数。(可选)如为 true,自动在 _id 字段创建索引。默认为 false。 |
size | 数值 | (可选)为固定集合指定一个最大值,即字节数。 如果 capped 为 true,也需要指定该字段。 |
max | 数值 | (可选)指定固定集合中包含文档的最大数量。 |
在插入文档时,MongoDB 首先检查固定集合的 size 字段,然后检查 max 字段。
查看集合
show tables
show collections
文档基本CURD
插入文档
其中
db.COLLECTION_NAME.insert(document)
或
db.COLLECTION_NAME.save(document)
- save():如果 _id 主键存在则更新数据,如果不存在就插入数据。该方法新版本中已废弃,可以使用 db.collection.insertOne() 或 db.collection.replaceOne() 来代替。
- insert(): 若插入的数据主键已经存在,则会抛 org.springframework.dao.DuplicateKeyException 异常,提示主键重复,不保存当前数据。
3.2 版本之后新增了 db.collection.insertOne() 和 db.collection.insertMany()。
db.collection.insertOne() 用于向集合插入一个新文档,语法格式如下:
db.collection.insertOne(
<document>,
{
writeConcern: <document>
}
)
db.collection.insertMany() 用于向集合插入一个多个文档,语法格式如下:
db.collection.insertMany(
[ <document 1> , <document 2>, ... ],
{
writeConcern: <document>,
ordered: <boolean>
}
)
参数说明:
- document:要写入的文档。
- writeConcern:写入策略,默认为 1,即要求确认写操作,0 是不要求。
- ordered:指定是否按顺序写入,默认 true,按顺序写入。
更新文档
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
}
)
参数说明:
- query : update的查询条件,类似sql update查询内where后面的。
- update : update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的
- upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
- multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
- writeConcern :可选,抛出异常的级别。
修改也分为两种
一个是覆盖修改
db.my.update({"_id":"1"},{name:"yhy3"})
覆盖修改会导致只剩下修改字段,最新版本里面好像移除了这个覆盖修改,会报错
MongoInvalidArgumentError: Update document requires atomic operators
一个是局部的修改
db.my.update({"_id":"1"},{$set:{name:"yhy3"}})
批量修改
以上语句只会修改第一条发现的文档,如果你要修改多条相同的文档,则需要设置 multi 参数为 true。
db.col.update({'title':'MongoDB 教程'},{$set:{'title':'MongoDB'}},{multi:true})
删除文档
删除 status 等于 D 的一个文档:
会优先删除插入时间早的文档.
db.inventory.deleteOne( { status: "D" } )
删除 status 等于 A 的全部文档:
db.inventory.deleteMany({ status : "A" })
如删除集合下全部文档:
db.inventory.deleteMany({})
查询文档
db.collection.find(query, projection)
- query :可选,使用查询操作符指定查询条件
- projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下:
db.col.find().pretty()
除了 find() 方法之外,还有一个 findOne() 方法,它只返回一个文档。
MongoDB和SQL的条件语句比较
操作 | 格式 | 范例 | RDBMS中的类似语句 |
---|---|---|---|
等于 | {<key>:<value> } | db.col.find({"by":"菜鸟教程"}).pretty() | where by = '菜鸟教程' |
小于 | {<key>:{$lt:<value>}} | db.col.find({"likes":{$lt:50}}).pretty() | where likes < 50 |
小于或等于 | {<key>:{$lte:<value>}} | db.col.find({"likes":{$lte:50}}).pretty() | where likes <= 50 |
大于 | {<key>:{$gt:<value>}} | db.col.find({"likes":{$gt:50}}).pretty() | where likes > 50 |
大于或等于 | {<key>:{$gte:<value>}} | db.col.find({"likes":{$gte:50}}).pretty() | where likes >= 50 |
不等于 | {<key>:{$ne:<value>}} | db.col.find({"likes":{$ne:50}}).pretty() | where likes != 50 |
and条件
db.col.find({key1:value1, key2:value2}).pretty()
or条件
db.col.find(
{
$or: [
{key1: value1}, {key2:value2}
]
}
).pretty()
文档的分页查询
聚合查询
db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
表达式 | 描述 | 实例 |
---|---|---|
$sum | 计算总和。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}]) |
$avg | 计算平均值 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}]) |
$min | 获取集合中所有文档对应值得最小值。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}]) |
$max | 获取集合中所有文档对应值得最大值。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}]) |
$push | 将值加入一个数组中,不会判断是否有重复的值。 | db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}]) |
$addToSet | 将值加入一个数组中,会判断是否有重复的值,若相同的值在数组中已经存在了,则不加入。 | db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}]) |
$first | 根据资源文档的排序获取第一个文档数据。 | db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}]) |
$last | 根据资源文档的排序获取最后一个文档数据 | db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}]) |
分页列表查询
skip用于跳过前面的页数,limit用于指定页面大小,差不多就是这个用法。
排序查询
文档的更多查询
正则的复杂条件查询,比较查询,包含查询,条件连接查询, 等的用到再去查吧。
索引
常用命令
4.索引
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构
创建索引
db.collection.createIndex(keys, options)
语法中 Key 值为你要创建的索引字段,1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可。
db.col.createIndex({"title":1})
createIndex() 方法中你也可以设置使用多个字段创建索引(关系型数据库中称作复合索引)。
db.col.createIndex({"title":1,"description":-1})
createIndex() 接收可选参数,可选参数列表如下:
Parameter | Type | Description |
---|---|---|
background | Boolean | 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false。 |
unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默认值为false. |
name | string | 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。 |
dropDups | Boolean | 3.0+版本已废弃。在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false. |
sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false. |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。 |
v | index version | 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。 |
weights | document | 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。 |
default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语 |
language_override | string | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language. |
在后台创建索引:
db.values.createIndex({open: 1, close: 1}, {background: true})
查看集合索引
db.col.getIndexes()
查看集合索引大小
db.col.totalIndexSize()
删除集合所有索引
db.col.dropIndexes()
删除集合指定索引
db.col.dropIndex("索引名称")
查看查询计划
你可以使用explain
方法来查看MongoDB执行查询的计划,以确保它使用了正确的索引。例如:
db.yourCollection.find({ field_name: "your_value" }).explain("")
使用索引进行查询可以看见下面的IXSCAN,表示的是索引扫描
5.文章评论案例
需求分析
表结构分析
技术选型
mongodb-dirver
springDataMongoDB
文章微服务模块搭建
新建一个maven模块,导入mongodb的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
编写配置文件
spring:
data:
mongodb:
host: 127.0.0.1
database: articledb
port: 27017
#也可以通过uri的方式配置
#uri: mongodb://127.0.0.1:27017/admin?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.1.0
创建文章评论实体类
/**
* 文章评论实体类
*/
//把一个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 + '\'' +
'}';
}
}
文章评论的增删改查
public interface CommentRepository extends MongoRepository<Comment,String> {
}
@Service
public class CommentService {
@Resource
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();
}
}
编写测试类测试增删改查
@SpringBootTest
class ArticleApplicationTests {
@Autowired
private CommentService commentService;
@Test
void testSave() {
Comment comment = new Comment();
comment.setContent("这是一条评论");
comment.setPublishtime(new Date());
comment.setUserid("user123");
comment.setNickname("John Doe");
comment.setCreatedatetime(LocalDateTime.now());
comment.setLikenum(0);
comment.setReplynum(0);
comment.setState("active");
comment.setParentid(null);
comment.setArticleid("article123");
commentService.saveComment(comment);
}
@Test
void testselectList(){
System.out.println(commentService.findCommentList());// 返回一个数组
System.out.println(commentService.findCommentById("65740c95da8e264ac2db678f"));//返回一个对象
}
@Test
void testUpdate(){
Comment comment = commentService.findCommentById("65740c95da8e264ac2db678f");
System.out.println("原本的点赞数:"+comment.getLikenum());
comment.setLikenum(comment.getLikenum()+1); //将点赞数+1
commentService.updateComment(comment);
comment=commentService.findCommentById("65740c95da8e264ac2db678f");
System.out.println("新的点赞数:"+comment.getLikenum());
}
@Test
void testDelete(){
commentService.deleteCommentById("65740c95da8e264ac2db678f");
}
}
根据上级id查询文章评论的分页列表
dao层新建接口方法
这里方法名一定要是这个findByParentid,否则会报错
package com.yhy.article.dao;
import com.yhy.article.pojo.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> {
Page<Comment> findByParentid(String parentid,Pageable pageable);
}
服务层调用
public Page<Comment> findCommentListByParentid(String parentid, int page, int size) {
return commentRepository.findByParentid(parentid, PageRequest.of(page-1,size));
}
测试类编写
@Test
void testFindCommentListByParentId(){
Page<Comment> page = commentService.findCommentListByParentid("3", 1, 2);
System.out.println(
page.getTotalElements()
);
System.out.println(page.getContent());
}
实现评论点赞
简单代码
正确代码
public void updateCommentLikenum(String id){
// 查询条件
Query query = Query.query(Criteria.where("_id").is(id));
// 更新条件
Update update = new Update();
update.inc("likenum");
//也可以设置更多的更新
//update.set("content","这是新的评论.");
//参数1.查询条件
//参数2.更新条件
//参数3.集合的名字或者实体类的类型Comment.class
mongoTemplate.updateFirst(query,update,Comment.class);
}