思想导图:

MongoDB快速上手(非常详细)

课程目标

MongoDB的副本集: 操作, 首要概念, 毛病搬运, 选举规矩 MongoDB的分片集群:概念, 长处, 操作, 分片战略, 毛病搬运 MongoDB的安全认证

  • 了解 MongoDB 的事务场景, 了解 MongoDB 的简介, 特色和体系结构, 数据类型等.
  • 能够在 Windows 和 Linux 下装置和发动 MongoDB, 图形化办理界面 Compass 的装置运用
  • 把握 MongoDB 根本常用指令完成数据的 CRUD
  • 把握 MongoDB 的索引类型, 索引办理, 履行方案

1. MongoDB 相关概念

1.1 事务场景

传统的联系型数据库 (比方 MySQL), 在数据操作的”三高”需求以及对应的 Web 2.0 网站需求面前, 会有”力不从心”的感觉

所谓的三高需求:

高并发, 高性能, 高可用, 简称三高

  • High Performance: 对数据库的高并发读写的要求
  • High Storage: 对海量数据的高功率存储和访问的需求
  • High Scalability && High Available: 对数据的高扩展性和高可用性的需求

而 MongoDB 能够应对三高需求

具体的运用场景:

  • 社交场景, 运用 MongoDB 存储用户信息, 以及用户发表的朋友圈信息, 经过地舆位置索引完成附近的人, 地址等功能.
  • 游戏场景, 运用 MongoDB 存储游戏用户信息, 用户的配备, 积分等直接以内嵌文档的办法存储, 便利查询, 高功率存储和访问.
  • 物流场景, 运用 MongoDB 存储订单信息, 订单状况在运送过程中会不断更新, 以 MongoDB 内嵌数组的办法来存储, 一次查询就能将订单一切的变更读取出来.
  • 物联网场景, 运用 MongoDB 存储一切接入的智能设备信息, 以及设备汇报的日志信息, 并对这些信息进行多维度的剖析.
  • 视频直播, 运用 MongoDB 存储用户信息, 点赞互动信息等.

这些运用场景中, 数据操作方面的共同点有:

  1. 数据量大
  2. 写入操作频频
  3. 价值较低的数据, 对事务性要求不高

关于这样的数据, 更适合用 MongoDB 来完成数据存储

那么咱们什么时分挑选 MongoDB 呢?

除了架构选型上, 除了上述三个特色之外, 还要考虑下面这些问题:

  • 运用不需求事务及杂乱 JOIN 支撑
  • 新运用, 需求会变, 数据模型无法确定, 想快速迭代开发
  • 运用需求 2000 – 3000 以上的读写QPS(更高也能够)
  • 运用需求 TB 乃至 PB 等级数据存储
  • 运用发展迅速, 需求能快速水平扩展
  • 运用要求存储的数据不丢掉
  • 运用需求 99.999% 高可用
  • 运用需求大量的地舆位置查询, 文本查询

假如上述有1个契合, 能够考虑 MongoDB, 2个及以上的契合, 挑选 MongoDB 绝不会懊悔.

假如上述事务场景运用用MySQL呢?

明显相关于MySQL, MongoDB能够以更低的本钱处理问题(包括学习, 开发, 运维等本钱)

1.2 MongoDB 简介

MongoDB是一个开源, 高性能, 无办法的文档型数据库, 当初的设计便是用于简化开发和便利扩展, 是NoSQL数据库产品中的一种.是最 像联系型数据库(MySQL)的非联系型数据库. 它支撑的数据结构非常松懈, 是一种相似于 JSON 的 格局叫BSON, 所以它既能够存储比较杂乱的数据类型, 又恰当的灵敏. MongoDB中的记载是一个文档, 它是一个由字段和值对(field:value)组成的数据结构.MongoDB文档相似于JSON目标, 即一个文档认 为便是一个目标.字段的数据类型是字符型, 它的值除了运用根本的一些类型外, 还能够包括其他文档, 一般数组和文档数组.

“最像联系型数据库的 NoSQL 数据库”. MongoDB 中的记载是一个文档, 是一个 key-value pair. 字段的数据类型是字符型, 值除了运用根本的一些类型以外, 还包括其它文档, 一般数组以及文档数组

Mongo和Mysql比照

MongoDB快速上手(非常详细)

MongoDB快速上手(非常详细)

MongoDB 数据模型是面向文档的, 所谓文档便是一种相似于 JSON 的结构, 简略了解 MongoDB 这个数据库中存在的是各式各样的 JSON(BSON)

  • 数据库 (database)
    • 数据库是一个库房, 存储调集 (collection)
  • 调集 (collection)
    • 相似于数组, 在调集中寄存文档
  • 文档 (document)
    • 文档型数据库的最小单位, 一般状况, 咱们存储和操作的内容都是文档

在 MongoDB 中, 数据库和调集都不需求手动创立, 当咱们创立文档时, 假如文档地点的调集或许数据库不存在, 则会主动创立数据库或许调集

1.2.1 数据库 (databases) 办理语法

操作 语法
检查一切数据库 show dbs;show databases;
检查当时数据库 db;
切换到某数据库 (若数据库不存在则创立数据库) use <db_name>;
删去当时数据库 db.dropDatabase();

1.2.2 调集 (collection) 办理语法

操作 语法
检查一切调集 show collections;
创立调集 db.createCollection("<collection_name>");
删去调集 db.<collection_name>.drop()

1.3. 数据类型

MongoDB的最小存储单位便是文档(document)目标。文档(document)目标对应于联系型数据库的行。数据在MongoDB中以 BSON(Binary-JSON)文档的格局存储在磁盘上。

BSON(Binary Serialized Document Format)是一种类json的一种二进制办法的存储格局,简称Binary JSON。BSON和JSON相同,支撑 内嵌的文档目标和数组目标,但是BSON有JSON没有的一些数据类型,如DateBinData类型。

BSON选用了相似于 C 言语结构体的称号、对标明办法,支撑内嵌的文档目标和数组目标,具有轻量性、可遍历性、高效性的三个特色,能够有用描述非结构化数据和结构化数据。这种格局的长处是灵敏性高,但它的缺陷是空间利用率不是很理想。

Bson中,除了根本的JSON类型:string,integer,boolean,double,null,array和object,mongo还运用了特别的数据类型。这些类型包括 date,object id,binary data,regular expression 和code。每一个驱动都以特定言语的办法完成了这些类型,检查你的驱动的文档来获取详细信息。

MongoDB快速上手(非常详细)

提示: shell默许运用64位浮点型数值。{“x”:3.14}或{“x”:3}。关于整型值,能够运用NumberInt(4字节符号整数)或NumberLong(8字节符 号整数),{“x”:NumberInt(“3”)}{“x”:NumberLong(“3”)}

1.4 MongoDB 的特色

1.4.1 高性能

MongoDB 供给高性能的数据耐久化

  • 嵌入式数据模型的支撑减少了数据库体系上的 I/O 活动
  • 索引支撑更快的查询, 而且能够包括来自嵌入式文档和数组的键 (文本索引处理搜索的需求, TTL 索引处理历史数据主动过期的需求, 地舆位置索引能够用于构件各种 O2O 运用)
  • mmapv1, wiredtiger, mongorocks (rocksdb) in-memory 等多引擎支撑满意各种场景需求
  • Gridfs 处理文件存储需求

1.4.2 高可用

MongoDB 的复制东西称作副本集 (replica set) 能够供给主动毛病搬运和数据冗余

1.4.3 高扩展

水平扩展是其间心功能一部分

分片将数据散布在一组集群的机器上 (海量数据存储, 服务能力水平扩展)

MongoDB 支撑依据片键创立数据区域, 在一个平衡的集群当中, MongoDB 将一个区域所掩盖的读写只定向到该区域的那些片

1.4.4 丰厚的查询支撑

MongoDB支撑丰厚的查询言语, 支撑读和写操作(CRUD), 比方数据聚合, 文本搜索和地舆空间查询等.

1.4.5 其他

无办法(动态办法), 灵敏的文档模型

2. 根本常用指令

MongoDB装置请看本文的第八点

2.1 数据库操作

操作 语法
检查一切数据库 show dbs;show databases;
检查当时数据库 db;
切换到某数据库 (若数据库不存在则创立数据库) use <db_name>;
删去当时数据库 db.dropDatabase();

默许保存的数据库

  • admin: 从权限角度考虑, 这是 root 数据库, 假如将一个用户增加到这个数据库, 这个用户主动承继一切数据库的权限, 一些特定的服务器端指令也只能从这个数据库运行, 比方列出一切的数据库或许关闭服务器

  • local: 数据永久不会被复制, 能够用来存储限于本地的单台服务器的调集 (布置集群, 分片等)

  • config: Mongo 用于分片设置时, config 数据库在内部运用, 用来保存分片的相关信息

    > show dbs
    admin   0.000GB
    config  0.000GB
    local   0.000GB
    > use articledb
    switched to db articledb
    > show dbs
    admin   0.000GB
    config  0.000GB
    local   0.000GB
    

当咱们创立了一个数据库后再进行检查会发现,咱们创立的数据库并没有显示出来,这是因为MongoDD的存储机制决议的

当运用 use articledb 的时分. articledb 其实寄存在内存之中, 当 articledb 中存在一个 collection 之后, mongo 才会将这个数据库耐久化到硬盘之中.

MongoDB快速上手(非常详细)

> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
> use articledb
switched to db articledb
> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
> db.articledb.insertOne({"a": 3})
{
        "acknowledged" : true,
        "insertedId" : ObjectId("62e128b6a70e7344a5139207")
}
> show dbs
admin      0.000GB
articledb  0.000GB
config     0.000GB
local      0.000GB

别的: 数据库名能够是满意以下条件的恣意UTF-8字符串。

  • 不能是空字符串(””)。
  • 不得含有' '空格)、.$/\\0 (空字符)。
  • 应悉数小写。
  • 最多64字节。

调集操作与数据库操作相似,这儿不再独自演示

MongoDB快速上手(非常详细)

2.2 文档根本 CRUD

官方文档: docs.mongodb.com/manual/crud…

2.2.1 创立 Create

Create or insert operations add new documents to a collection. If the collection does not currently exist, insert operations will create the collection automatically.

文档的数据结构和 JSON 根本相同。

一切存储在调集中的数据都是 BSON 格局。

BSON 是一种相似 JSON 的二进制办法的存储格局,是 Binary JSON 的简称

  • 运用 db.<collection_name>.insertOne() 向调集中增加一个文档, 参数一个 json 格局的文档 -db.collection.insertOne() 用于向调集刺进一个新文档,语法格局如下:

    db.collection.insertOne(
     <document>,
     {
        writeConcern: <document>
     }
    )
    
  • 运用 db.<collection_name>.insertMany() 向调集中增加多个文档, 参数为 json 文档数组 db.collection.insertMany() 用于向调集刺进一个多个文档,语法格局如下:

db.collection.insertMany(
   [ <document 1> , <document 2>, ... ],
   {
      writeConcern: <document>,
      ordered: <boolean>
   }
)

参数阐明:

  • document:要写入的文档。
  • writeConcern:写入战略,默许为 1,即要求承认写操作,0 是不要求。
  • ordered:指定是否按次序写入,默许 true,按次序写入

咱们平常运用最多的只要document这一个字段

MongoDB快速上手(非常详细)

#  刺进单条数据
> var document = db.collection.insertOne({"a": 3})
> document
{
        "acknowledged" : true,
        "insertedId" : ObjectId("571a218011a82a1d94c02333")
}
#  刺进多条数据
> var res = db.collection.insertMany([{"b": 3}, {'c': 4}])
> res
{
        "acknowledged" : true,
        "insertedIds" : [
                ObjectId("571a22a911a82a1d94c02337"),
                ObjectId("571a22a911a82a1d94c02338")
        ]
}

还能够经过js函数办法批量刺进文档:

1、先创立数组 2、将数据放在数组中 3、一次 insert 到调集中

var arr = [];
for(var i=1 ; i<=20000 ; i++){
    arr.push({num:i});
}
db.numbers.insert(arr);

注:当咱们向 collection 中刺进 document 文档时, 假如没有给文档指定 _id 特点, 那么数据库会为文档主动增加 _id field, 而且值类型是 ObjectId(blablabla), 便是文档的仅有标识, 相似于 relational database 里的 primary key

  • mongo 中的数字, 默许状况下是 double 类型, 假如要存整型, 有必要运用函数 NumberInt(整型数字), 否则取出来就有问题了
  • 刺进当时日期能够运用 new Date()

假如某条数据刺进失利, 将会停止刺进, 但现已刺进成功的数据不会回滚掉. 因为批量刺进因为数据较多容易呈现失利, 因而, 能够运用 try catch 进行异常捕捉处理, 测验的时分能够不处理.如:

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);
}

2.2.2 查询 Read

更多查询能够看2.4节和2.5节

  • 运用 db.<collection_name>.find() 办法对调集进行查询, 接受一个 json 格局的查询条件. 回来的是一个数组
  • db.<collection_name>.findOne() 查询调集中契合条件的第一个文档, 回来的是一个目标

MongoDB快速上手(非常详细)

// 刺进多条记载
> 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"}
]);
{
        "acknowledged" : true,
        "insertedIds" : [
                "1",
                "2",
                "3",
                "4",
                "5"
        ]
}
// 只回来查询到的第一条数据
> db.comment.findOne({"articleid":"100001"})
{
        "_id" : "1",
        "articleid" : "100001",
        "content" : "咱们不应该把清晨糟蹋在手机上,健康很重要,一杯温水幸福你我他。",
        "userid" : "1002",
        "nickname" : "相忘于江湖",
        "createdatetime" : ISODate("2019-08-05T22:08:15.522Z"),
        "likenum" : 1000,
        "state" : "1"
}
// 等价于
db.comment.find({"articleid":"100001"}).limit(1)

假如咱们不需求那么多的字段,咱们能够在查询条件后面再跟上需求查询的字段,1标明显示指定的字段,其间_id是默许显示的,咱们指定0标明强制不显示

// 只显示articleid字段
> db.comment.find({"articleid":"100001"},{"articleid":1}).limit(1)
{ "_id" : "1", "articleid" : "100001" }
// 强制_id不显示
> db.comment.find({"articleid":"100001"},{"articleid":1,"_id":0}).limit(1)
{ "articleid" : "100001" }

能够运用 $in 操作符标明范围查询

db.inventory.find( { status: { $in: [ "A", "D" ] } } )

多个查询条件用逗号分隔, 标明 AND 的联系

db.inventory.find( { status: "A", qty: { $lt: 30 } } )

等价于下面 sql 句子

SELECT * FROM inventory WHERE status = "A" AND qty < 30

运用 $or 操作符标明后边数组中的条件是OR的联系

db.inventory.find( { $or: [ { status: "A" }, { qty: { $lt: 30 } } ] } )

等价于下面 sql 句子

SELECT * FROM inventory WHERE status = "A" OR qty < 30

联合运用 ANDOR 的查询句子

db.inventory.find( {
     status: "A",
     $or: [ { qty: { $lt: 30 } }, { item: /^p/ } ]
} )

在 terminal 中检查成果或许不是很便利, 所以咱们能够用 pretty() 来帮助阅览

db.inventory.find().pretty()

匹配内容

db.posts.find({
  comments: {
    $elemMatch: {
      user: 'Harry Potter'
    }
  }
}).pretty()
// 正则表达式
db.<collection_name>.find({ content : /once/ })

创立索引

db.posts.createIndex({
  { title : 'text' }
})
// 文本搜索
// will return document with title "Post One"
// if there is no more posts created
db.posts.find({
  $text : {
    $search : "\"Post O\""
  }
}).pretty()

2.2.3 更新 Update

  • 运用 db.<collection_name>.updateOne(<filter>, <update>, <options>) 办法批改一个匹配 <filter> 条件的文档
  • 运用 db.<collection_name>.updateMany(<filter>, <update>, <options>) 办法批改一切匹配 <filter> 条件的文档
  • 运用 db.<collection_name>.replaceOne(<filter>, <update>, <options>) 办法替换一个匹配 <filter> 条件的文档
  • db.<collection_name>.update(查询目标, 新目标) 默许状况下会运用新目标替换旧目标

其间 <filter> 参数与查询办法中的条件参数用法一致

掩盖批改,会将其他的值铲除

// nModified1标明有一条记载被批改
> db.comment.update({"_id":"1"}, {"likenum":NumberInt(1001)})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
// 能够看到其他字段的值不见了
> db.comment.find()
{ "_id" : "1", "likenum" : 1001 }
{ "_id" : "2", "articleid" : "100001", "content" : "我夏天空腹喝凉开水,冬季喝温开水", "userid" : "1005", "nickname" : "伊人憔悴", "createdatetime" : ISODate("2019-08-05T23:58:51.485Z"), "likenum" : 888, "state" : "1" }
{ "_id" : "3", "articleid" : "100001", "content" : "我一向喝凉开水,冬季夏天都喝。", "userid" : "1004", "nickname" : "杰克船长", "createdatetime" : ISODate("2019-08-06T01:05:06.321Z"), "likenum" : 666, "state" : "1" }
{ "_id" : "4", "articleid" : "100001", "content" : "专家说不能空腹吃饭,影响健康。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("2019-08-06T08:18:35.288Z"), "likenum" : 2000, "state" : "1" }
{ "_id" : "5", "articleid" : "100001", "content" : "研讨标明,刚烧开的水千万不能喝,因为烫嘴。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("2019-08-06T11:01:02.521Z"), "likenum" : 3000, "state" : "1" }

部分批改,只批改咱们批改的部分,其他字段不受影响

假如需求批改指定的特点, 而不是替换需求用“批改操作符”来进行批改

  • $set 批改文档中的制定特点
// 发现部分批改后其他字段并不受影响
> db.comment.update({ "_id": "2" }, {$set:{ "likenum": NumberInt(1001) }})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
> db.comment.find()
{ "_id" : "1", "likenum" : 1001 }
{ "_id" : "2", "articleid" : "100001", "content" : "我夏天空腹喝凉开水,冬季喝温开水", "userid" : "1005", "nickname" : "伊人憔悴", "createdatetime" : ISODate("2019-08-05T23:58:51.485Z"), "likenum" : 1001, "state" : "1" }
{ "_id" : "3", "articleid" : "100001", "content" : "我一向喝凉开水,冬季夏天都喝。", "userid" : "1004", "nickname" : "杰克船长", "createdatetime" : ISODate("2019-08-06T01:05:06.321Z"), "likenum" : 666, "state" : "1" }
{ "_id" : "4", "articleid" : "100001", "content" : "专家说不能空腹吃饭,影响健康。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("2019-08-06T08:18:35.288Z"), "likenum" : 2000, "state" : "1" }
{ "_id" : "5", "articleid" : "100001", "content" : "研讨标明,刚烧开的水千万不能喝,因为烫嘴。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("2019-08-06T11:01:02.521Z"), "likenum" : 3000, "state" : "1" }

其间最常用的批改操作符即为$set$unset,别离标明赋值撤销赋值.

db.inventory.updateOne(
    { item: "paper" },
    {
        $set: { "size.uom": "cm", status: "P" },
        $currentDate: { lastModified: true }
    }
)
db.inventory.updateMany(
    { qty: { $lt: 50 } },
    {
        $set: { "size.uom": "in", status: "P" },
        $currentDate: { lastModified: true }
    }
)
  • uses the $set operator to update the value of the size.uom field to "cm" and the value of the status field to "P",
  • uses the $currentDate operator to update the value of the lastModified field to the current date. If lastModified field does not exist, $currentDate will create the field. See $currentDate for details.

db.<collection_name>.replaceOne() 办法替换除 _id 特点外的一切特点, 其<update>参数应为一个全新的文档.

db.inventory.replaceOne(
    { item: "paper" },
    { item: "paper", instock: [ { warehouse: "A", qty: 60 }, { warehouse: "B", qty: 40 } ] }
)

批量批改

在后面增加{multi: true}即可

// 默许会批改第一条
db.commnet.update({ userid: "30", { $set {username: "guest"} } })
// 批改一切契合条件的数据
db.commnet.update( { userid: "30", { $set {username: "guest"} } }, {multi: true} )

MongoDB快速上手(非常详细)

MongoDB快速上手(非常详细)

列值增加的批改

假如咱们想完成对某列值在原有值的基础上进行增加或减少, 能够运用 $inc 运算符来完成

db.commnet.update({ _id: "3", {$inc: {likeNum: NumberInt(1)}} })

MongoDB快速上手(非常详细)

批改操作符

Name Description
$currentDate Sets the value of a field to current date, either as a Date or a Timestamp.
$inc Increments the value of the field by the specified amount.
$min Only updates the field if the specified value is less than the existing field value.
$max Only updates the field if the specified value is greater than the existing field value.
$mul Multiplies the value of the field by the specified amount.
$rename Renames a field.
$set Sets the value of a field in a document.
$setOnInsert Sets the value of a field if an update results in an insert of a document. Has no effect on update operations that modify existing documents.
$unset Removes the specified field from a document.

2.2.4 删去 Delete

  • db.collection.remove()经过增加删去规矩进行删去
  • 运用 db.collection.deleteMany() 办法删去一切匹配的文档.
  • 运用 db.collection.deleteOne() 办法删去单个匹配的文档.
  • db.collection.drop()
  • db.dropDatabase()

只删去一条记载

MongoDB快速上手(非常详细)

假如不加后面的约束会删去一切匹配的记载

以下句子能够将数据悉数删去,请慎用

db.comment.remove({})

Delete operations do not drop indexes, even if deleting all documents from a collection.

一般数据库中的数据都不会真正意义上的删去, 会增加一个字段, 用来标明这个数据是否被删去

2.3 文档排序和投影 (sort & projection)

2.3.1 排序 Sort

在查询文档内容的时分, 默许是依照 _id 进行排序

咱们能够用 $sort 更改文档排序规矩

{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }

For the field or fields to sort by, set the sort order to 1 or -1 to specify an ascending or descending sort respectively, as in the following example:

db.users.aggregate(
   [
     { $sort : { age : -1, posts: 1 } }
     // ascending on posts and descending on age
   ]
)

$sort Operator and Memory

$sort + $limit Memory Optimization

When a $sort precedes a $limit and there are no intervening stages that modify the number of documents, the optimizer can coalesce the $limit into the $sort. This allows the $sort operation to only maintain the top n results as it progresses, where n is the specified limit, and ensures that MongoDB only needs to store n items in memory. This optimization still applies when allowDiskUse is true and the n items exceed the aggregation memory limit.

Optimizations are subject to change between releases.

有点相似于用 heap 做 topK 这种问题, 只保护 k 个大小的 heap, 会加速 process

举个栗子:

db.posts.find().sort({ title : -1 }).limit(2).pretty()

2.3.2 投影 Projection

有些状况, 咱们对文档进行查询并不是需求一切的字段, 比方只需求 id 或许 用户名, 咱们能够对文档进行“投影”

  • 1 – display
  • 0 – dont display
> db.users.find( {}, {username: 1} )
> db.users.find( {}, {age: 1, _id: 0} )

2.4 分页查询

2.4.1 计算查询

计算查询运用count()办法,语法如下:

db.collection.count(query, options)

参数:

MongoDB快速上手(非常详细)

提示: 可选项暂时不运用。

【示例】

(1)计算一切记载数: 计算comment调集的一切的记载数:

MongoDB快速上手(非常详细)

(2)按条件计算记载数:例如:计算userid为1003的记载条数

MongoDB快速上手(非常详细)

提示: 默许状况下 count() 办法回来契合条件的悉数记载条数。

2.4.2 分页列表查询

能够运用limit()办法来读取指定数量的数据,运用skip()办法来越过指定数量的数据

根本语法如下所示:

db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)

MongoDB快速上手(非常详细)

2.4.3 排序查询

sort() 办法对数据进行排序,sort() 办法能够经过参数指定排序的字段,并运用 1 和 -1 来指定排序的办法,其间 1 为升序摆放,而 -1 是用 于降序摆放。

语法如下所示:

db.COLLECTION_NAME.find().sort({KEY:1})
或 
db.调集称号.find().sort(排序办法)

例如: 对userid降序摆放,并对访问量进行升序摆放

MongoDB快速上手(非常详细)

提示: skip(), limilt(), sort()三个放在一起履行的时分,履行的次序是先 sort(), 然后是 skip(),最终是显示的 limit(),和指令编写次序无关。

2.5 其他查询办法

2.5.1 正则表达式(含糊查询)

MongoDB的含糊查询是经过正则表达式的办法完成的。格局为

$ db.collection.find({field:/正则表达式/})
$ db.collection.find({字段:/正则表达式/})

提示:正则表达式是js的语法,直接量的写法。 例如,我要查询谈论内容包括“开水”的一切文档,代码如下:

MongoDB快速上手(非常详细)

假如要查询谈论的内容中以“专家”最初的,代码如下:

MongoDB快速上手(非常详细)

附录:常用的正则表达式

MongoDB快速上手(非常详细)

MongoDB快速上手(非常详细)

MongoDB快速上手(非常详细)

2.5.2 比较查询

<, <=, >, >= 这些操作符也是很常用的, 格局如下:

其实这些字符便是对应JS里面的:gt(great than)、lt(less than)、gte(great than equal )、lte(less than equal )、ne(not equal)

db.collection.find({ "field" : { $gt: value }}) // 大于: field > value
db.collection.find({ "field" : { $lt: value }}) // 小于: field < value
db.collection.find({ "field" : { $gte: value }}) // 大于等于: field >= value
db.collection.find({ "field" : { $lte: value }}) // 小于等于: field <= value
db.collection.find({ "field" : { $ne: value }}) // 不等于: field != value

示例:查询谈观点赞数量大于700的记载

MongoDB快速上手(非常详细)

2.5.3 包括查询

包括运用 $in 操作符. 示例:查询谈论的调集中 userid 字段包括 10031004的文档

db.comment.find({userid:{$in:["1003","1004"]}})

MongoDB快速上手(非常详细)

不包括运用 $nin 操作符. 示例:查询谈论调集中 userid 字段不包括 10031004 的文档

db.comment.find({userid:{$nin:["1003","1004"]}})

MongoDB快速上手(非常详细)

2.5.4 条件衔接查询

咱们假如需求查询一起满意两个以上条件,需求运用$and操作符将条件进行相关。(恰当于SQL的and) 格局为:

$and:[ { },{ },{ } ]

示例:查询谈论调集中likenum大于等于700 而且小于2000的文档:

db.comment.find({$and:[{likenum:{$gte:NumberInt(700)}},{likenum:{$lt:NumberInt(2000)}}]})

MongoDB快速上手(非常详细)

假如两个以上条件之间是或许的联系,咱们运用 操作符进行相关,与前面 and的运用办法相同 格局为:

$or:[ { },{ },{ } ]

示例:查询谈论调集中userid为1003,或许点赞数小于1000的文档记载

db.comment.find({$or:[ {userid:"1003"} ,{likenum:{$lt:1000} }]})

MongoDB快速上手(非常详细)

2.5.5 foreach查询

咱们知道这些查询句子其实便是js的语法格局,一切在查询得到成果后咱们也能够经过forEach函数对成果进行遍历

db.posts.find().forEach(
    fucntion(doc) { 
        print('Blog Post: ' + doc.title) 
    })
// 也能够经过箭头函数简化一下
db.comment.find().forEach((it)=> { 
      print(it._id)
});

MongoDB快速上手(非常详细)

2.5.6 地舆位置查询

请检查MongoDB中文文档:地舆空间查询 – MongoDB-CN-Manual (mongoing.com)

2.6 常用指令小结

挑选切换数据库:use articledb
刺进数据:db.comment.insert({bson数据})
查询一切数据:db.comment.find();
条件查询数据:db.comment.find({条件})
查询契合条件的第一条记载:db.comment.findOne({条件})
查询契合条件的前几条记载:db.comment.find({条件}).limit(条数)
查询契合条件的越过的记载:db.comment.find({条件}).skip(条数)
批改数据:db.comment.update({条件},{批改后的数据})
        或
        db.comment.update({条件},{$set:{要批改部分的字段:数据})
批改数据并自增某字段值:db.comment.update({条件},{$inc:{自增的字段:步进值}})
删去数据:db.comment.remove({条件})
计算查询:db.comment.count({条件})
含糊查询:db.comment.find({字段名:/正则表达式/})
条件比较运算:db.comment.find({字段名:{$gt:值}})
包括查询:db.comment.find({字段名:{$in:[值1, 值2]}})
        或
        db.comment.find({字段名:{$nin:[值1, 值2]}})
条件衔接查询:db.comment.find({$and:[{条件1},{条件2}]})
           或
           db.comment.find({$or:[{条件1},{条件2}]})

3. 文档间的对应联系

  • 1对1 (One To One)
  • 一对多/多对一(one to many / many to one)
  • 多对多 (Many To Many)

3.1 1对1

在MongoDB中能够经过内嵌文档的办法表现出1对1的联系,比方夫妻:

{
    name:'黄蓉',
    husband:{
        name:'郭靖'
    }
}

一个文档目标一旦被嵌入到另一个文档目标中就绝不或许再被嵌入到其他文档目标中,因而能够表现出1对1的联系

3.2 一对多/多对一

一对多的联系在实践开发中是非常常用的,也是现实国际中呈现频率比较高的联系

有两种办法能够表现一对多(或多对一)的联系,以客户和订单为例:

一:联系在一的一方保护,直接经过内嵌数组,在数组中寄存整个目标的办法:这种办法欠好,因为假如对应的目标比较多的话,文档就会看起来很杂乱,不易查询

{
    cust_id:ObjectId("5d272c817f2dc9e6986d82fb"),
    cust_name:"黑宋江",
    orders:[
        {
            _id: ObjectId("5d2614c42b1a4fdfd82bfda3"),
            type:"牛肉",
            count:2
        },
        {
            _id:ObjectId("5d272c817f2dc9e6986d82fa"),
            type:"酒",
            count:6
        }
    ]
}

二:一对多,用户:constom/订单orders

举个比方, 比方“用户-订单”这个一对多的联系中, 咱们想查询某一个用户的一切或许某个订单, 咱们能够在用户中增加订单的主键

先创立用户调集

db.constom.insert([
    {username:'孙悟空'},
    {username:'猪八戒'}
])

再创立订单调集(增加一个userid特点,该订单是谁的就给userid特点增加谁的_id)

db.orders.insert({
    list:["辣椒","花椒","油"],
    userid:ObjectId("5ebcfe39bc5756d0fed31ff3")//这个是孙悟空的_id代表该订单便是孙悟空的。
})

经过userid再去查找每个人对应的订单

var userid = db.constom.findOne({username:'孙悟空'})._id;
db.orders.find({userid:userid})

MongoDB快速上手(非常详细)

3.3 多对多

在联系型数据库中咱们处理多对多联系的时分选用的办法一般是将两张表的主键抽取出来,放到一张独自的联系表中,将两张表的主键作为这张联系表的外键,每次做相关查询的时分都要先到这张联系表中找出对应表的主键

在MongoDB中多对多选用的其实是相似与一对多的状况,也是经过增加一些冗余的字段来记载联系

举个比方,咱们在联系型数据库中一般会以学生和教师作为比方,这儿相同咱们也举这个:

//多对多
// 先刺进一些教师的信息
db.teachers.insertMany([
    {name:"洪七公"},
    {name:"黄药师"},
    {name:"龟仙人"}
]);
db.teachers.find();
// 刺进一些学生的信息,而且将教师的id进行记载
db.students.insertMany([
    {
        name:"郭靖",
        teachers_ids:[
        ObjectId("5d7f018b162f56aeed8aedda"),
        ObjectId("5d7f018b162f56aeed8aeddb"),
        ObjectId("5d7f018b162f56aeed8aeddc")
        ]
    },{
        name:"黄蓉",
        teachers_ids:[
        ObjectId("5d7f018b162f56aeed8aedda"),
        ObjectId("5d7f018b162f56aeed8aeddb"),
        ObjectId("5d7f018b162f56aeed8aeddc")
        ]
    }
]);
db.students.find();

4. MongoDB 的索引

4.1 概述

索引支撑在 MongoDB 中高效地履行查询.假如没有索引, MongoDB 有必要履行全调集扫描, 即扫描调集中的每个文档, 以挑选与查询句子 匹配的文档.这种扫描全调集的查询功率是非常低的, 特别在处理大量的数据时, 查询能够要花费几十秒乃至几分钟, 这对网站的性能是非常丧命的.

假如查询存在恰当的索引, MongoDB 能够运用该索引约束有必要检查的文档数.

索引是特别的数据结构, 它以易于遍历的办法存储调集数据集的一小部分.索引存储特定字段或一组字段的值, 按字段值排序.索引项的排 序支撑有用的持平匹配和依据范围的查询操作.此外, MongoDB 还能够运用索引中的排序回来排序成果.

MongoDB 和MySQL 相同运用的都是是 B+ Tree

在之前的版别中Mongo运用的是B树,但是现在都是运用B+树了

  • Mongo官方文档
  • 为什么Mongo挑选过B树?

索引常用指令:

// create index
db.<collection_name>.createIndex({ userid : 1, username : -1 })
// retrieve indexes
db.<collection_name>.getIndexes()
// remove indexes
db.<collection_name>.dropIndex(index)
// there are 2 ways to remove indexes:
// 1. removed based on the index name
// 2. removed based on the fields
db.<collection_name>.dropIndex( "userid_1_username_-1" )
db.<collection_name>.dropIndex({ userid : 1, username : -1 })
// remove all the indexes, will only remove non_id indexes
db.<collection_name>.dropIndexes()

4.2 索引的类型

4.2.1 单字段索引

MongoDB 支撑在文档的单个字段上创立用户界说的升序/降序索引, 称为单字段索引(Single Field Index)

关于单个字段索引和排序操作, 索引键的排序次序(即升序或降序)并不重要, 因为 MongoDB 能够在任何方向上遍历索引.

MongoDB快速上手(非常详细)

4.2.2 复合索引

MongoDB 还支撑多个字段的用户界说索引, 即复合索引 Compound Index,这个其实非常相似MySQL中的联合索引,因为底层都是B+树,一切联合索引或许也有最左原则这种东西

复合索引中列出的字段次序具有重要意义.例如, 假如复合索引由 { userid: 1, score: -1 } 组成, 则索引首先按 userid 正序排序, 然后 在每个 userid 的值内, 再在按 score 倒序排序.

MongoDB快速上手(非常详细)

4.2.3 其他索引

  • 地舆空间索引 Geospatial Index
  • 文本索引 Text Indexes
  • 哈希索引 Hashed Indexes
地舆空间索引(Geospatial Index)

为了支撑对地舆空间坐标数据的有用查询, MongoDB 供给了两种特别的索引: 回来成果时运用平面几何的二维索引和回来成果时运用球面几何的二维球面索引.

文本索引(Text Indexes)

MongoDB 供给了一种文本索引类型, 支撑在调集中搜索字符串内容.这些文本索引不存储特定于言语的停止词(例如 “the”, “a”, “or”), 而将调集中的词作为词干, 只存储根词.

哈希索引(Hashed Indexes)

为了支撑依据散列的分片, MongoDB 供给了散列索引类型, 它对字段值的散列进行索引.这些索引在其范围内的值散布更加随机, 但只支撑持平匹配, 不支撑依据范围的查询.

4.3 索引的办理操作

4.3.1 索引的检查

语法

db.collection.getIndexes()

提示:该语法指令运行要求是MongoDB 3.0+

【示例】 检查comment调集中一切的索引状况

MongoDB快速上手(非常详细)

成果中显示的是默许 _id 索引。

默许 _id 索引: MongoDB 在创立调集的过程中, 在 _id 字段上创立一个仅有的索引, 默许姓名为 _id , 该索引可防止客户端刺进两个具有相同值的文 档, 不能在 _id 字段上删去此索引.

留意:该索引是仅有索引, 因而值不能重复, 即 _id 值不能重复的.

在分片集群中, 一般运用 _id 作为片键.

4.3.2 索引的创立

语法

db.collection.createIndex(keys, options)

参数

MongoDB快速上手(非常详细)

options(更多选项)列表

MongoDB快速上手(非常详细)

留意在 3.0.0 版别前创立索引办法为 db.collection.ensureIndex() , 之后的版别运用了 db.collection.createIndex() 办法, ensureIndex() 还能用, 但只是 createIndex() 的别名.

举个

userid:1 标明由userid依照升序创立索引,userid:1,nickname:-1}标明先按userid升序,假如userid持平再依照nickname降序创立索引,这儿和MySQL一摸相同

// 先由userid依照升序创立索引
$  db.comment.createIndex({userid:1})
{
  "createdCollectionAutomatically" : false,
  "numIndexesBefore" : 1,
  "numIndexesAfter" : 2,
  "ok" : 1
}
// 先按userid升序,假如userid持平再依照nickname降序创立索引
$ db.comment.createIndex({userid:1,nickname:-1})
...

MongoDB快速上手(非常详细)

MongoDB快速上手(非常详细)

4.3.3 索引的删去

语法

# 删去某一个索引
$ db.collection.dropIndex(index)
# 删去悉数索引
$ db.collection.dropIndexes()

其间index类型为:string or document,标明指定要删去的索引。能够经过索引称号或索引标准文档指定索引。若要删去文本索引,请指定索引称号。

提示:

_id 的字段的索引是无法删去的, 只能删去非 _id 字段的索引

示例

# 删去 comment 调集中 userid 字段上的升序索引
$ db.comment.dropIndex({userid:1})

MongoDB快速上手(非常详细)

4.4 索引运用

4.4.1 履行方案

剖析查询性能 (Analyze Query Performance) 一般运用履行方案 (解释方案 – Explain Plan) 来检查查询的状况

$ db.<collection_name>.find( query, options ).explain(options)

比方: 检查依据 user_id 查询数据的状况

未增加索引之前

"stage" : "COLLSCAN", 标明全调集扫描

MongoDB快速上手(非常详细)

增加索引之后

"stage" : "IXSCAN", 依据索引的扫描

MongoDB快速上手(非常详细)

4.4.2 掩盖索引查询(Covered Queries)

这儿假如直接看的话其实仍是满难了解的,但是假如咱们结合MySQL来看就会发现,这不便是MySQL里面的掩盖索引吗?掩盖索引不便是减少了回表操作吗?这样的话其实一下就能了解下面的介绍,看来知识都是相通的,仍是应该多学底层,运用层的东西会变,但是底层的东西大部分都不会改动,你看AVL树、红黑树这些数据结构,都是上个世纪中期发生的,现在用的仍是这一套

当查询条件和查询的投影仅包括索引字段时, MongoDB 直接从索引回来成果, 而不扫描任何文档或将文档带入内存, 这些掩盖的查询非常有用

MongoDB快速上手(非常详细)

docs.mongodb.com/manual/core…

例如咱们查询下面的索引履行方案:

MongoDB快速上手(非常详细)

5. SpringBoot整合MongoDB

5.1 需求剖析

这儿会结合一个具体的事务场景(小事例),对用户谈论进行CRUD

MongoDB快速上手(非常详细)

在这个事例中首要的需求是:

  • 根本增删改查API
  • 依据文章id查询谈论
  • 谈观点赞

文章示例参阅:早晨空腹喝水,是对仍是错?www.toutiao.com/a6721476546…

5.2 表结构剖析

数据库:articledb,调集就用咱们上面一向在运用的comment

MongoDB快速上手(非常详细)

5.3 技能选型

5.3.1 mongodb-driver(了解)

mongodb-driver是mongo官方推出的java衔接mongoDB的驱动包,恰当于JDBC驱动。咱们经过一个入门的事例来了解mongodb-driver 的根本运用。

  • 官方驱动阐明和下载:mongodb.github.io/mongo-java-…
  • 官方驱动示例文档:mongodb.github.io/mongo-java-…

5.3.2 SpringDataMongoDB

SpringData宗族成员之一,用于操作MongoDB的耐久层结构,封装了底层的mongodb-driver。

官网主页: projects.spring.io/spring-data…

5.4 文章微服务模块树立

(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 https://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.7.2</version>  
        <relativePath/> <!-- lookup parent from repository -->  
    </parent>  
    <groupId>com.fx</groupId>  
    <artifactId>article</artifactId>  
    <version>0.0.1-SNAPSHOT</version>  
    <name>article</name>  
    <description>Demo project for Spring Boot</description>  
    <properties>  
        <java.version>1.8</java.version>  
    </properties>  
    <dependencies>  
        <dependency>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-data-mongodb</artifactId>  
        </dependency>  
        <dependency>  
            <groupId>org.projectlombok</groupId>  
            <artifactId>lombok</artifactId>  
            <optional>true</optional>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-test</artifactId>  
            <scope>test</scope>  
        </dependency>  
    </dependencies>  
    <build>  
        <plugins>  
            <plugin>  
                <groupId>org.springframework.boot</groupId>  
                <artifactId>spring-boot-maven-plugin</artifactId>  
                <configuration>  
                    <excludes>  
                        <exclude>  
                            <groupId>org.projectlombok</groupId>  
                            <artifactId>lombok</artifactId>  
                        </exclude>  
                    </excludes>  
                </configuration>  
            </plugin>  
        </plugins>  
    </build>  
</project>

(2)创立application.yml

spring:
  #数据源装备  
  data:  
    mongodb:  
      # 主机地址  
      host: localhost  
      # 数据库  
      database: articledb  
      # 默许端口是27017  
      port: 27017  
      # 也能够运用uri衔接  
      #uri: mongodb://192.168.40.134:27017/articledb

(3)创立发动类

com.fx.article.ArticleApplication

package com.fx.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);  
    }  
}

咱们发动一下空项目,看Mongo衔接是否正常,一般就能够正常衔接了

MongoDB快速上手(非常详细)

5.5 文章谈论实体类的编写

创立实体类 创立包com.fx.article,包下建包po用于寄存实体类,创立实体类

这儿有一点需求留意,因为Mongo是能够进行横向拓展的,所以或许会呈现一个调集对应多个实体类的状况

package com.fx.article.po;
import lombok.Data;  
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 java.io.Serializable;  
import java.time.LocalDateTime;  
import java.util.Date;  
/**  
 * <p>  
 * 文档谈论实体类<br/>  
 * 把一个Java类生命为mongodb的文档,能够经过collection参数指定这个类对应的文档  
 * </p>  
 *  
 * @since 2022-07-28 20:19  
 * @author 梁峰源  
 **/  
@Data  
// 若未增加@Document注解,则该bean save到mongo的comment collection  
@Document(collection = "comment")//指定对应的调集,假如省略则默许为类名小写进行调集映射  
@CompoundIndex(def = "{'userid': 1, 'nickname' : -1}") // 增加复合索引,先按userid排,再按nickname降序排  
public class Comment implements Serializable {  
    private static final long serialVersionUID = 21218312631312334L;  
    // 主键标识,该特点的值会主动对应mongodb的主键字段`_id`, 假如该特点名叫做 `id`, 则该注解能够省略,否者有必要写  
    @Id  
    private String id;//主键  
    private String content;//吐槽内容  
    private Date publishtime;//发布日期  
    // 增加了一个单子段的索引  
    @Indexed  
    private String userid;//发布人的ID  
    private String nickname;//用户昵称  
    private LocalDateTime createdatetime;//谈论的日期时间  
    private Integer likenum;//点赞数  
    private Integer replaynum;//回复数  
    private String state;//状况  
    private String parentid;//上级ID  
    private String articleid;//文章id  
}

阐明: 索引能够大大提升查询功率,一般在查询字段上增加索引,索引的增加能够经过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})

5.6 文章谈论的根本增删改查

(1)创立数据访问接口 cn.itcast.article包下创立dao包,包下创立接口

com.fx.article.dao.CommentRepository

MongoDB快速上手(非常详细)

package com.fx.article.dao;
import com.fx.article.po.Comment;  
import org.springframework.data.mongodb.repository.MongoRepository;  
/**  
 * <p>  
 *  
 * </p>  
 *  
 * @author 梁峰源  
 * @since 2022-07-28 20:34  
 **/public interface CommentRepository extends MongoRepository<Comment, String> {  
}

(2)创立事务逻辑类 cn.itcast.article包下创立service包,包下创立类

com.fx.article.service.CommentService

package com.fx.article.service;
import com.fx.article.dao.CommentRepository;  
import com.fx.article.po.Comment;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  
import java.util.List;  
/**  
 * <p>  
 * 谈论service办法  
 * </p>  
 *  
 * @since 2022-07-28 20:36  
 * @author 梁峰源  
 **/  
@Service  
public class CommentService {  
    //注入dao  
    @Autowired  
    private CommentRepository commentRepository;  
    /**  
     * 保存一个谈论  
     */  
    public void saveComment(Comment comment) {  
        //假如需求自界说主键,能够在这儿指定主键;假如不指定主键,MongoDB会主动生成主键  
        //设置一些默许初始值。。。  
        //调用dao  
        commentRepository.save(comment);  
    }  
    /**  
     * 更新谈论  
     */  
    public void updateComment(Comment comment) {  
        //调用dao  
        commentRepository.save(comment);  
    }  
    /**  
     * 依据id删去谈论  
     */  
    public void deleteCommentById(String id) {  
        //调用dao  
        commentRepository.deleteById(id);  
    }  
    /**  
     * 查询一切谈论  
     */  
    public List<Comment> findCommentList() {  
        //调用dao  
        return commentRepository.findAll();  
    }  
    /**  
     * 依据id查询谈论  
     */  
    public Comment findCommentById(String id) {  
        //调用dao  
        return commentRepository.findById(id).get();  
    }  
}

(3)新建Junit测验类,测验保存和查询一切:

com.fx.itcast.article.service.CommentServiceTest

package com.fx.article.service;
import com.fx.article.po.Comment;  
import org.junit.jupiter.api.Test;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.boot.test.context.SpringBootTest;  
import java.time.LocalDateTime;  
import java.util.List;  
/**  
 * @author Eureka  
 * @version 1.0  
 * @since 2022年7月28日20:56:33  
 */@SpringBootTest  
public class CommentServiceTest {  
    @Autowired  
    private CommentService commentService;  
    /**  
     * 保存一条记载  
     */  
    @Test  
    public void testSaveComment() throws Exception {  
        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);  
        // 在查询一下这条记载,这儿获取id的办法和Mybatis Plus相同,直接从实体类中获取即可  
        Comment commentById = commentService.findCommentById(comment.getId());  
        System.out.println(commentById);  
    }  
    /**  
     * 更新一条记载  
     */  
    @Test  
    public void testUpdateComment() throws Exception {  
//        Comment comment = new Comment();  
//        comment.setId("1");  
//        comment.setNickname("张三");  
        // 上面的办法会将其他字段变为空,所以能够先查询出来再更新对应字段  
        Comment comment = commentService.findCommentById("1");  
        comment.setNickname("张三");  
        // 更新这条记载  
        commentService.updateComment(comment);  
        // 打印一下这条记载  
        Comment commentById = commentService.findCommentById(comment.getId());  
        System.out.println(commentById);  
    }  
    /**  
     * Method: deleteCommentById(String id)     */    @Test  
    public void testDeleteCommentById() throws Exception {  
        commentService.deleteCommentById("1");  
    }  
    /**  
     * Method: findCommentList()     */    @Test  
    public void testFindCommentList() throws Exception {  
        List<Comment> commentList = commentService.findCommentList();  
        System.out.println(commentList);  
    }  
    /**  
     * 经过id查询一条记载  
     */  
    @Test  
    public void testFindCommentById() throws Exception {  
        Comment commentById = commentService.findCommentById("1");  
        System.out.println(commentById);  
    }  
}

这儿需求留意假如是MongoDB 6以上的版别或许打印不出来,这儿或许有像MySQL中MVCC之类的同学,我换成4版别后就能够正常打印出来了

MongoDB快速上手(非常详细)

5.7 依据上级ID查询文章谈论的分页列表

(1)CommentRepository新增办法界说

/**
 * 分页查询,这儿的姓名要依据提示来,不能写错不然会生成失利  
 */  
Page<Comment> findByUserid(String userid, Pageable pageable);

(2)CommentService新增办法

/**
 * 依据父id查询分页列表  
 * @param userid  
 * @param page 页码  
 * @param size 页数  
 */  
public Page<Comment> findCommentListPageByUserid(String userid,int page ,int size){  
    return commentRepository.findByUserid(userid, PageRequest.of(page-1,size));  
}

测验

@Test
void testFindCommentListPageByParentid() {  
    Page<Comment> pages = commentService.findCommentListPageByUserid("1003", 1, 3);  
    // 打印有多少条记载  
    System.out.println(pages.getTotalElements());  
    List<Comment> contentList = pages.getContent();  
    // 将一切的记载都打印出来  
    contentList.forEach(System.out::println);  
}

5.8 MongoTemplate完成谈观点赞

咱们看一下以下点赞的临时示例代码: 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);    update.inc("likenum");  
    //参数1:查询目标  
    //参数2:更新目标  
    //参数3:调集的姓名或实体类的类型Comment.class  
    mongoTemplate.updateFirst(query, update, "comment");  
    }

(2)测验用例:

@Test
void testupdateCommentLikenum() {  
    // 更新之前  
    System.out.println(commentService.findCommentById("2"));  
    commentService.updateCommentLikenum("2");  
    // 更新之后  
    System.out.println(commentService.findCommentById("2"));  
}

测验成果,能够看到数据现已增加了

MongoDB快速上手(非常详细)

更多的指令能够自行进行检查,这儿贴一下API的地址:

  • 菜鸟教程
  • Spring-data MongoDB官方文档

6. 在 Nodejs 中运用 MongoDB – mongoose

mongoose 是一个目标文档模型(ODM)库

mongoosejs.com/

  • 能够为文档创立一个办法结构(Schema)
  • 能够对模型中的目标/文档进行验证
  • 数据能够经过类型转换转换为目标模型
  • 能够运用中间件运用事务逻辑

6.1 mongoose 供给的新目标类型

  • Schema
    • 界说约束了数据库中的文档结构
    • 个人感觉相似于 SQL 中建表时事先规则表结构
  • Model
    • 调集中的一切文档的标明, 恰当于 MongoDB 数据库中的 collection
  • Document
    • 标明调集中的具体文档, 恰当于调集中的一个具体的文档

6.2 简略运用 Mongoose

mongoosejs.com/docs/guide.…

运用 mongoose 回来的是一个 mogoose Query object, mongoose 履行 query 句子后的成果会被传进 callback 函数 callback(error, result)

A mongoose query can be executed in one of two ways. First, if you pass in a callback function, Mongoose will execute the query asynchronously and pass the results to the callback.

A query also has a .then() function, and thus can be used as a promise.

const q = MyModel.updateMany({}, { isDeleted: true }, function() {
  console.log("Update 1");
}));
q.then(() => console.log("Update 2"));
q.then(() => console.log("Update 3"));

上面这一段代码会履行三次 updateMany() 操作, 第一次是因为 callback, 之后的两次是因为 .then() (因为 .then() 也会调用 updatemany())

衔接数据库而且创立 Model 类

const mongoose = require('mongoose');
// test is the name of database, will be created automatically
mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true});
const Cat = mongoose.model('Cat', { name: String });
const kitty = new Cat({ name: 'Zildjian' });
kitty.save().then(() => console.log('meow'));

监听 MongoDB 数据库的衔接状况

在 mongoose 目标中, 有一个特点叫做 connection, 该目标就标明数据库衔接.经过监视该目标的状况, 能够来监听数据库的衔接和端口

mongoose.connection.once("open", function() {
  console.log("connection opened.")
});
mongoose.connection.once("close", function() {
  console.log("connection closed.")
});

6.3 Mongoose 的 CRUD

首先界说一个 Schema

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const blogSchema = new Schema({
    title:  String, // String is shorthand for {type: String}
    author: String,
    body:   String,
    comments: [{ body: String, date: Date }],
    date: { type: Date, default: Date.now },
    hidden: Boolean,
    meta: {
        votes: Number,
        favs:  Number
    }
});

然后在 blogSchema 基础上创立 Model

const Blog = mongoose.model('Blog', blogSchema);
// ready to go!
module.exports = Blog;

当调用上面这一行代码时, MongoDB 会做如下操作

  1. 是否存在一个数据库叫做 Blog 啊? 没的话那就创立一个
  2. 每次用到 Blog 库的时分都要留意内部数据要依照 blogSchema 来规则

向数据库中刺进文档数据

Blog.create({
  title: "title"
  ...
}, function (err){
  if (!err) {
    console.log("successful")
  }
});

简略的查询一下下

// named john and at least 18 yo
MyModel.find({ name: 'john', age: { $gte: 18 }});

mongoose 支撑的用法有:

  • Model.deleteMany()
  • Model.deleteOne()
  • Model.find()
  • Model.findById()
  • Model.findByIdAndDelete()
  • Model.findByIdAndRemove()
  • Model.findByIdAndUpdate()
  • Model.findOne()
  • Model.findOneAndDelete()
  • Model.findOneAndRemove()
  • Model.findOneAndReplace()
  • Model.findOneAndUpdate()
  • Model.replaceOne()
  • Model.updateMany()
  • Model.updateOne()

7. 运用 Mocha 编写测验 “Test Driven Development”

Mocha 是一个 js 测验的包, 编写测验有两个关键字 describeit

  • describe 是一个”统领块”, 一切的 test functions 都会在它”名下”
  • it 标明每一个 test function
create_test.js
const assert = require('assert')
// assume we have a User model defined in src/user.js
const User = require('../src/user')
// after installing Mocha, we have global access
// to describe and it keywords
describe('Creating records', () => {
  it('saves a user', () => {
    const joe = new User({ name: "Joe" });
    joe.save();
    assert()
  });
});

NoSQL Databases

Benefits of NoSQL

  • Easy for inserting and retrieving data, since they are contained in one block, in one json object
  • Flexible schema, if a new attribute added, it is easy to just add / append to the object
  • Scalability, horizontally partition the data (availability > consistency)
  • Aggregation, find metrics and etc

Drawbacks of NoSQL

  • Update = Delete + Insert, not built for update
  • Not consistent, ACID is not guaranteed, do not support transactions
  • Not read optimized. Read entire block find the attribute. But SQL, just need one column (read time compartively slow)
  • Relations are not implicit
  • JOINS are hard to accomplish, all manually

8. MongoDB单机布置(win + docker)

8.1 Windows体系中的装置发动

第一步:下载装置包

MongoDB 供给了可用于 32 位和 64 位体系的预编译二进制包,你能够从MongoDB官网下载装置,MongoDB 预编译二进制包下载地址:Install MongoDB — MongoDB Manual

这儿主张下载4.0.12版别的,高版别的有些指令用不了

MongoDB快速上手(非常详细)

依据上图所示下载 zip 包。

提示:版别的挑选: MongoDB的版别命名标准如:x.y.z;

y为奇数时标明当时版别为开发版,如:1.5.2、4.1.13;

y为偶数时标明当时版别为稳定版,如:1.6.3、4.0.10; z是批改版别号,数字越大越好。

详情:docs.mongodb.org/manual/rele…

第二步:解压装置发动

将压缩包解压到一个目录中。 进入bin目录,运用mongod -version检查版别

MongoDB快速上手(非常详细)

在解压目录中,手动树立一个目录用于寄存数据文件,如 data/db

办法1:指令行参数办法发动服务

在 bin 目录中翻开指令行提示符,输入如下指令:

mongod --dbpath=..\data\db

MongoDB快速上手(非常详细)

咱们在发动信息中能够看到,mongoDB的默许端口是27017,假如咱们想改动默许的发动端口,能够经过–port来指定端口。

为了便利咱们每次发动,能够将装置目录的bin目录设置到环境变量的path中, bin 目录下是一些常用指令,比方 mongod 发动服务用的, mongo 客户端衔接服务用的。

MongoDB快速上手(非常详细)

办法2:装备文件办法发动服务

在解压目录中新建 config 文件夹,该文件夹中新建装备文件 mongod.conf ,内如参阅如下:

storage:
    #The directory where the mongod instance stores its data.Default Value is "\data\db" on Windows. 
    dbPath: D:\Environment\MongoDB\mongodb-6.0.0\data

详细装备项内容能够参阅官方文档: docs.mongodb.com/manual/refe…

【留意1】装备文件中假如运用双引号,比方途径地址,主动会将双引号的内容转义

处理: a. 对 \ 换成 / 或 \ b. 假如途径中没有空格,则无需加引号。

【留意2】装备文件中不能以Tab分割字段

处理: 将其转换成空格。

发动办法:

mongod -f ../config/mongod.conf 或 mongod --config ../config/mongod.conf

更多参数装备:

systemLog:
  destination: file  
  #The path of the log file to which mongod or mongos should send all diagnostic logging information  
  path: "D:/02_Server/DBServer/mongodb-win32-x86_64-2008plus-ssl-4.0.1/log/mongod.log"  
  logAppend: true  
storage:  
  journal:  
  enabled: true  
  #The directory where the mongod instance stores its data.Default Value is "/data/db".  
  dbPath: "D:/02_Server/DBServer/mongodb-win32-x86_64-2008plus-ssl-4.0.1/data"  
  net:  
  #bindIp: 127.0.0.1  
  port: 27017  
setParameter:  
  enableLocalhostAuthBypass: false

8.2 Shell衔接(mongo指令)

假如你需求进入MongoDB后台办理,你需求先翻开mongodb装目录的下的bin目录,然后履行mongo.exe文件,MongoDB Shell是MongoDB自带的交互式Javascript shell,用来对MongoDB进行操作和办理的交互式环境。

./mongo 或 ./mongo --host=127.0.0.1 --port=27017

这儿高版别的Mongo bin目录下没有mongo.exe文件,这儿我换成4.0.12版别的了

MongoDB快速上手(非常详细)

当你进入mongoDB后台后,它默许会链接到 test 文档(数据库):

能够测验一下常用的Mongo指令

> show databases
admin   0.000GB
config  0.000GB
local   0.000GB

检查当时的数据库:

> db
test

因为它是一个JavaScript shell,您能够运行一些简略的算术运算:

> 2 + 2
4

8.3 docker装置发动

参阅装置地址

8.4 Compass-图形化界面客户端

到MongoDB官网下载MongoDB Compass

地址:www.mongodb.com/download-ce…

假如是下载装置版,则依照过程装置;假如是下载加压缩版,直接解压,履行里面的 MongoDBCompassCommunity.exe 文件即可。

在翻开的界面中,输入主机地址、端口等相关信息,点击衔接:

MongoDB快速上手(非常详细)

我司运用的东西是noSqlBooster,感觉也挺好用的:www.nosqlbooster.com/

References

  • mongoosejs.com/docs/guides…
  • docs.mongodb.com/
  • www.bilibili.com/video/av596…
  • www.bilibili.com/video/BV1bJ…
  • www.youtube.com/watch?v=-56…