文章目录
MongoDB
学习笔记
MongoDB
是一个开源、高性能、无模式的文档型数据库,当初的设计就是用于简化开发和方便扩展,是NoSQL
数据库产品中的一种。是最 像关系型数据库(MySQL
)的非关系型数据库。 它支持的数据结构非常松散,是一种类似于JSON
的 格式叫BSON
,所以它既可以存储比较复杂的数据类型,又相当的灵活。MongoDB
中的记录是一个文档,它是一个由字段和值对(field:value
)组成的数据结构。MongoDB
文档类似于JSON
对象,即一个文档认 为就是一个对象。字段的数据类型是字符型,它的值除了使用基本的一些类型外,还可以包括其他文档、普通数组和文档数组。
1. 体系结构
1.1 与传统关系型数据库对比
SQL | MongoDB | 说明 |
---|---|---|
database | database | 数据库 |
table | connection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | filed | 数据字段/域 |
index | index | 索引 |
join | 表连接 | |
嵌入文档 | 通过嵌入文档来替代多表连接 | |
primary key | primary key | 主键/MongoDB 自动将_id设为主键 |
2. 数据模型
MongoDB
的最小存储单位就是文档(document
)对象。文档(document
)对象对应于关系型数据库的行。数据在MongoDB
中以
BSON(Binary-JSON)
文档的格式存储在磁盘上。
BSON(Binary Serialized Document Format)
是一种类json
的一种二进制形式的存储格式,简称Binary JSON
。BSON
和JSON
一样,支持内嵌的文档对象和数组对象,但是BSON
有JSON
没有的一些数据类型,如Date
和BinData
类型。BSON
采用了类似于 C 语言结构体的名称、对表示方法,支持内嵌的文档对象和数组对象,具有轻量性、可遍历性、高效性的三个特点,可以有效描述非结构化数据和结构化数据。这种格式的优点是灵活性高,但它的缺点是空间利用率不是很理想。
BSON
中,除了基本的JSON
类型:string,integer,boolean,double,null,array
和object,mongo
还使用了特殊的数据类型。这些类型包括date,object id,binary data,regular expression 和code。每一个驱动都以特定语言的方式实现了这些类型,查看你的驱动的文档来获取详细信息。
数据类型 | 描述 | 说明 |
---|---|---|
字符串 | UTF-8 字符串都可表示为字符串类型的数据 | {"x": "string"} |
对象ID | 对象ID是文档的12字节的唯一ID | {"X": ObjectId()} |
布尔值 | true / false | {"x": true/false} |
数组 | 值的集合列表 | {”x": ["a","b","c","d"]} |
数字 | {“x”: 3.14156926,"y": 3} | |
null | 表示空值或未定义的对象 | {“x”: null} |
undefined | JavaScript中的未定义类型 | {"x": undefined} |
正则表达式 | JavaScript中的正则表达式 | {”x": /value/} |
代码 | JavaScript 代码 | {"x" : function() { /* …… */ }} |
shell默认使用64位浮点型数值。{“x”:3.14}或{“x”:3}。对于整型值,可以使用
NumberInt
(4字节符号整数)或NumberLong
(8字节符 号整数),{“x”:NumberInt(“3”)}{“x”:NumberLong(“3”)}
3. 特点
-
高性能
- 嵌入式的数据模型减少了文档操作
- 持久性
-
高可用
- 副本集
-
高可扩
- 数据分片
- 集群
-
丰富的查询
4. 安装
# 启动
./mongod -f 【配置文件】
# 停止
mongo
use admin
db.shutdownServer()
# 也可以使用kill(有一定的危险性)
5. 基本命令操作
# 连接
mongo
# mongo --help
# 数据库存在则切换反之创建
use 【数据库名称】
# 注意此时数据库是在内存中的,只有当次数据库有集合插入文档是数据库才会被持久化到磁盘
# 所以此时 show dbs;是看不到的
# 数据库列表
show dbs
5.1 数据库命名规范
- 不能是空字符串("")
- 不得含有’ '(空格)、.、$、/、\和\0 (空字符)
- 应全部小写
- 最多64字节
- 保留
admin
: 用户权限local
: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合config
: 当Mongo
用于分片设置时,config
数据库在内部使用,用于保存分片的相关信息
5.2 数据库操作
use 【数据库名称】
# 删除当前数据库
db.dropDatabase()
5.3 集合操作
5.3.1 集合名称命名规范
- 集合名不能是空字符串""
- 不能含有\0字符(空字符),这个字符表示集合名的结尾
- 不能以"system."开头,这是为系统集合保留的前缀
- 用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除 非你要访问这种系统创建的集合,否则千万不要在名字里出现$
# 创建集合
# 如果直接执行插入文档操作,集合不存在的话,会自动创建
# 切换数据库
db.createColletion(【集合名称】)
# 显示集合列表
show collections()
# 集合删除
db.【集合名称】.drop()
5.4 文档CRUD
5.4.1 文档插入
db.collection.insert( // or save
<document or array of documents>,// 插入到集合中的文档或者文档数组
{
writeConcern: <document>,
// 可选
// true:
// 按照顺序插入数据中的文档,如果其中一个文档出现错误则返回,不会处理其他的文档
// false
// 执行无序插入,如果其中一个文档出现错误,则继续处理其他文档。2.6+以后的默认为true
ordered: <boolean>
}
)
# 案例
# 没有指定ID ,ID会自动生成
db.comment.insert({"articleId": "1000","content":"talk is cheap show me the code","userId":"1001","nickName":"jack","createTime":new Date(),"likeNum":NumberInt(10),"state": null})
-
注意
- 文档中的键/值对是有序的
- 文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)
- 区分类型和大小写
- 键不能重复
- 文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符
-
键命名规范
- 键不能含有\0 (空字符)。这个字符用来表示键的结尾
- 和$有特别的意义,只有在特定环境下才能使用
- 以下划线"_"开头的键是保留的(不是严格要求的)
# 批量插入
# 设置了主键_id,所以主键就是该值
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"
}])
5.4.2 文档查询
# query: 可选,使用查询运算符指定选择筛选器。返回所有则忽略此参数或者传递{}
# projection: 可选,指定要在与查询筛选器匹配的文档中返回的字段(投影),返回所有则忽略(类似SQL select 之后筛选)
db.collection.find(<query>, [projection])
# 查询所有
db.comment.find()
db.comment.find({})
# 查询filed userId = 1001 的文档
db.comment.find({"userId": "1002"})
# 查询 filed nickName like 于
# 模糊查询 nickname 含有 于
db.comment.find({nickname: /于/}); # 正则表达式
# 查询以什么开头的
db.comment.find({content: /^我/})
# 根据条件查询,只返回第一个文档
db.comment.findOne({userId: '1003'})
# 投影查询
# 查询所有不显示_id(除了指定不显示的其他都显示)
db.comment.find({},{_id: 0})
# 只显示articleId
db.comment.find({},{_id: 0 ,articleId: 1})
5.4.3 文档更新
# query: 查询选择器与find中一致
# update: 要应用的修改(局部修改或替换文档)
# upsert: 可选:默认为false
# true: 没有匹配到文档时新建
# false: 没有匹配到修改也不进行插入
# multi: 可选,默认值false
# true: 更新符合条件的多个文档
# false: 更新符合条件的一个文档
# writeConcern: 抛出异常的级别
# collation: 校对规则
# arrayFilters: 筛选文档数组
# hint: 指定用于支持查询谓词的索引的文档或字符串
db.collection.update(query, update, options)
# 或
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>,
collation: <document>,
arrayFilters: [ <filterdocument1>, ... ],
hint: <document|string> // Available starting in MongoDB 4.2
}
)
# 覆盖更新,其他filed都不在了。只有新设置的
db.comment.update({ _id: '1' }, { likenum: NumberInt(24234) })
# 局部修改,其他字段还是原来的
db.comment.update({ _id: '2' }, { $set: { likenum: NumberInt(9999) } })
# 批量修改
# 默认只修改第一条数据
db.comment.update({userid:"1003"},{$set:{nickname:"凯撒2"}})
# 修改所有符合条件的数据
db.comment.update({userid:"1003"},{$set:{nickname:"凯撒大帝"}},{multi:true})
# 列值增长的修改,正数自增,负数自减
db.comment.update({_id: "1"},{$inc: {likenum: NumberInt(-1)}})
5.4.5文档删除
db.集合名称.remove(条件)
# 案例
# 删除所有
db.comment.remove({})
# 删除_id为1的记录
db.comment.remove({_id:"1"})
5.4.6 统计查询
# query: 查询选择条件
# options: 用于修改计数的额外选项
db.collection.count(query, options)
# 统计集合的所有记录条数
db.comment.count();
# 统计userid为1003的文档数
db.comment.count({userId: '1003'})
5.4.7 分页列表查询
# limit: 查询几条
# skip: 跳过几条
db.【集合名称】.find().limit(NUMBER).skip(NUMBER)
# 查询多少条记录 limit 默认20
db.comment.find()
.limit(2)
# 查询两条跳过前两条 3 4
db.comment.find()
.limit(2)
.skip(2)
5.4.8排序查询
db.集合名称.find().sort(排序方式)
# _id 降序
db.comment.find().sort({_id: -1})
# _id 降序
# likenum 升序
db.comment.find().sort({_id: -1, likenum: 1})
执行顺序: sort() > skip() > limit()
5.4.9 比较查询
db.集合名称.find({ "field" : { $gt: value }}) // 大于: field > value
db.集合名称.find({ "field" : { $lt: value }}) // 小于: field < value
db.集合名称.find({ "field" : { $gte: value }}) // 大于等于: field >= value
db.集合名称.find({ "field" : { $lte: value }}) // 小于等于: field <= value
db.集合名称.find({ "field" : { $ne: value }}) // 不等于: field != value
#筛选点赞数大于等于1000的文档
db.comment.find({likenum: {$gte: NumberInt(1000)}})
5.4.10 包含查询
# 在
db.comment.find({_id: {$in: ['1','2']}})
# 不在
db.comment.find({_id: {$nin: ['1','2']}})
5.4.11 条件连接查询
$and:[ { },{ },{ } ]
$or:[ { },{ },{ } ]
# 点赞数大于200并且小于1000
db.comment.find(
{
$and: [
{
likenum: {
$gt: NumberInt(200)
}
},
{
likenum: {
$lt: NumberInt(1000)
}
}
]
}
)
# userId = 1003 或者 点赞数大于等于200
db.comment.find(
{
$or: [
{
userId: '1003'
},
{
likenum: {
$gte: NumberInt(200)
}
}
]
}
)
5.5 聚合(aggregate)
5.5.1 准备数据
db.agg.insertMany(
[
{
"_id": ObjectId("5b0cf67270e4fa02d31de42e"),
"name": "rainbowSix Siege",
"time": 400.0
},
{
"_id": ObjectId("5b0cf69270e4fa02d31de42f"),
"name": "Assassin's creed",
"time": 20.0
},
{
"_id": ObjectId("5b0cf6ad70e4fa02d31de430"),
"name": "ghost Recon",
"time": 0.0
},
{
"_id": ObjectId("5b0d14c870e4fa02d31de436"),
"name": "farCry",
"time": 0.0
}
]
)
5.5.2 分组
# 执行
db.agg.aggregate(# 数组
[
{
$group: {
_id: '$time', # 分组依据
num: {$sum: 1} # 计数 +1
}
}
]
)
# 结果
/* 1 */
{
"_id" : 20,
"num" : 1
},
/* 2 */
{
"_id" : 400,
"num" : 1
},
/* 3 */
{
"_id" : 0,
"num" : 2
}
# 语句
db.agg.aggregate(
[
{
$group: {
_id: '$time',
getNameList: {
$push: '$name'
}
}
}
]
)
# 结果
/* 1 */
{
"_id" : 20,
"getNameList" : [
"Assassin's creed"
]
},
/* 2 */
{
"_id" : 400,
"getNameList" : [
"rainbowSix Siege"
]
},
/* 3 */
{
"_id" : 0,
"getNameList" : [
"ghost Recon",
"farCry"
]
}
5.5.3 过滤
# 过滤time >= 20 的document
db.agg.aggregate(
[
{
$match: {
time: {
$gte: 20
}
}
}
]
)
# 结果
/* 1 createdAt:2018/5/29 下午2:42:58*/
{
"_id" : ObjectId("5b0cf67270e4fa02d31de42e"),
"name" : "rainbowSix Siege",
"time" : 400
},
/* 2 createdAt:2018/5/29 下午2:43:30*/
{
"_id" : ObjectId("5b0cf69270e4fa02d31de42f"),
"name" : "Assassin's creed",
"time" : 20
}
5.5.4 投影
# 文档中只显示name
db.agg.aggregate(
[
{
$project: {
_id: 0,
name: 1
}
}
]
)
# 结果
/* 1 */
{
"name" : "rainbowSix Siege"
},
/* 2 */
{
"name" : "Assassin's creed"
},
/* 3 */
{
"name" : "ghost Recon"
},
/* 4 */
{
"name" : "farCry"
}
s o r t 、 sort、 sort、skip、$limit 与find中一致
5.5.5 拆分
# 新增数据
db.agg.insertOne(
{
"_id": ObjectId("5b0e242ed85f6f9cc56da7cc"),
"name": "gameList",
"list": [
"dota2",
"csgo",
"ow"
]
}
)
# 语句
db.agg.aggregate(
[
{
$unwind: '$list',
}
]
)
# 结果
/* 1 createdAt:2018/5/30 下午12:10:22*/
{
"_id" : ObjectId("5b0e242ed85f6f9cc56da7cc"),
"name" : "gameList",
"list" : "dota2"
},
/* 2 createdAt:2018/5/30 下午12:10:22*/
{
"_id" : ObjectId("5b0e242ed85f6f9cc56da7cc"),
"name" : "gameList",
"list" : "csgo"
},
/* 3 createdAt:2018/5/30 下午12:10:22*/
{
"_id" : ObjectId("5b0e242ed85f6f9cc56da7cc"),
"name" : "gameList",
"list" : "ow"
}
注意:aggregate 数组中可指定多个 $group $unwind $skip …
6.索引
6.1 单字段索引
MongoDB
支持在文档的单个字段上创建用户定义的升序/降序索引,称为单字段索引(Single Field Index)。对于单个字段索引和排序操作,索引键的排序顺序(即升序或降序)并不重要,因为MongoDB
可以在任何方向上遍历索引。
6.2 符合索引
MongoDB
还支持多个字段的用户定义索引,即复合索引(Compound Index)。
复合索引中列出的字段顺序具有重要意义。例如,如果复合索引由 {userId
1, score: -1 } 组成,则索引首先按userId
正序排序,然后在每个userId
的值内,再在按score倒序排序。
6.3 其他索引
- 地理空间索引(
Geospatial Index
)
为了支持对地理空间坐标数据的有效查询,
MongoDB
提供了两种特殊的索引:返回结果时使用平面几何的二维索引和返回结果时使用球面几何的二维球面索引。
- 文本索引(Text Indexes)
MongoDB
提供了一种文本索引类型,支持在集合中搜索字符串内容。这些文本索引不存储特定于语言的停止词(例如“the”、“a”、“or”),而将集合中的词作为词干,只存储根词。
- 哈希索引(Hashed Indexes)
为了支持基于散列的分片,
MongoDB
提供了散列索引类型,它对字段值的散列进行索引。这些索引在其范围内的值分布更加随机,但只支持相等匹配,不支持基于范围的查询。
6.4 索引的管理操作
6.4.1 查看索引
db.【集合名称】.getIndexes();
# 默认索引 _id
{
"v" : 2, # 版本
"key" : {
"_id" : 1
},
"name" : "_id_"
}
6.4.2 创建索引
db.【集合名称】.createIndex(keys, options)
Parameter | Type | Description |
---|---|---|
keys | document | 包含字段和值对的文档,其中字段是索引键,值描述该字段的索引类型。对于字段上的升序索引,请 指定值1;对于降序索引,请指定值-1。比如: {字段:1或-1} ,其中1 为指定按升序创建索引,如果你 想按降序来创建索引指定为 -1 即可。另外,MongoDB 支持几种不同的索引类型,包括文本、地理空 间和哈希索引。 |
options | document | 可选。包含一组控制索引创建的选项的文档。 |
options
Parameter | Type | Description |
---|---|---|
background | Boolean | 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 “background” 可选参数。 “background” 默认值为false。 |
unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默认值为false. |
name | string | 索引的名称。如果未指定,MongoDB 的通过连接索引的字段名和排序顺序生成一个索引名 称 |
sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索 引字段中不会查询出不包含对应字段的文档.。默认值为 false. |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间 |
v | index version | 索引的版本号。默认的索引版本取决于mongod 创建索引时运行的版本 |
weights | document | 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重 |
default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语 |
language_override | string | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language. |
注意在 3.0.0 版本前创建索引方法为
db.collection.ensureIndex()
,之后的版本使用了db.collection.createIndex()
方法,ensureIndex()
还能用,但只是createIndex()
的别名。
# 创建单个索引
db.comment.createIndex({userId: 1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
db.comment.createIndex({userId: 1, nickname: -1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
6.4.3 索引移除
# index
# 指定要删除的索引。可以通过索引名称或索引规范文档指定索引。若要删除文本索引,请指定索引名称
db.【集合名称】.dropIndex(index)
db.comment.dropIndex("userId_1")
# 删除所有索引 _id字段索引不能删除
db.comment.dropIndexes()
6.4.4 索引的使用
使用索引,索引扫描
未建索引,使用全集合扫描
6.4.4.1 执行计划
分析查询性能(Analyze Query Performance)通常使用执行计划(解释计划、Explain Plan)来查看查询的情况,如查询耗费的时间、是 否基于索引查询等。 那么,通常,我们想知道,建立的索引是否有效,效果如何,都需要通过执行计划查看。
db.【集合名称】.find(query,options).explain(options)
6.4.4.2 涵盖查询
当查询条件和查询的投影仅包含索引字段时,
MongoDB
直接从索引返回结果,而不扫描任何文档或将文档带入内存。 这些覆盖的查询可以非常有效。
7. SpringBoot2.x整合
7.1 依赖
<?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.5.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.dingwen</groupId>
<artifactId>stu-spr-boo-mon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>stu-spr-boo-mon</name>
<description>stu-spr-boo-mon</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--mongodb-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- AOP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</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>
7.2 实体
package com.dingwen.stusprboomon.entity;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.Id;
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;
/**
* 文章评论实体
* Document(collation = "comment"): 对应集合名称。可以省略默认使用类小写映射集合
* CompoundIndex( def = "{'userId': 1, 'nickname': -1}") 符合索引
* Id: 主键标识,该属性的值会自动对应mongodb的主键字段"_id",如果该属性名就叫“id”,则该注解可以省略,否则必须写
* Filed: 该属性对应mongodb的字段的名字,如果一致,则无需该注解
* Indexed: 添加一个单字段索引
*
* @author dingwen
* 2021.06.23 13:48
*/
@Document("comment")
@Getter
@Setter
@ToString
public class CommentEntity implements Serializable {
private static final long serialVersionUID = 9056057851721363099L;
@Id
private String id;
/**
* 文章的id
*/
@Field("articleId")
private String articleId;
/**
* 内容
*/
@Field("content")
private String content;
/**
* 用户id
*/
@Indexed
@Field("userId")
private String userId;
/**
* 点赞数
*/
@Field("likeNum")
private Integer likeNum;
/**
* 状态
*/
@Field("state")
private String state;
/**
* 昵称
*/
@Field("nickname")
private String nickname;
/**
* 创建时间
*/
@Field("createDatetime")
private LocalDateTime createDatetime;
}
7.3 mongoTemplate
封装
完整代码: https://gitee.com/dingwen-gitee/stu-spr-boo-mon.git
8. 安全
默认情况下,
MongoDB
实例启动运行时是没有启用用户访问权限控制的,也就是说,在实例本机服务器上都可以随意连接到实例进行各种操作,MongoDB
不会对连接客户端进行用户验证,这是非常危险的。为了强制开启用户访问控制(用户验证),则需要在MongoDB
实例启动时使用选项--auth
或在指定启动 配置文件中添加选项auth=true
。
8.1 概念
8.1.1 启用访问控制
MongoDB
使用的是基于角色的访问控制(Role-Based Access Control,RBAC
)来管理用户对实例的访问。通过对用户授予一个或多个角色来控制用户访问数据库资源的权限和数据库操作的权限,在对用户分配角色之前,用户无法访问实例。
8.1.2 角色
在
MongoDB
中通过角色对用户授予相应数据库资源的操作权限,每个角色当中的权限可以显式指定,也可以通过继承其他角色的权限,或者两都都存在的权限。
8.1.3 权限
权限由指定的数据库资源(resource)以及允许在指定资源上进行的操作(action)组成。
8.2 角色
# 角色信息查看
# 查询所有角色权限(仅用户自定义角色)
db.runCommand({ rolesInfo: 1 })
# 查询所有角色权限(包含内置角色)
db.runCommand({ rolesInfo: 1, showBuiltinRoles: true })
# 查询当前数据库中的某角色的权限
db.runCommand({ rolesInfo: "<rolename>" })
# 查询其它数据库中指定的角色权限
db.runCommand({ rolesInfo: { role: "<rolename>", db: "<database>" } }
# 查询多个角色权限
db.runCommand(
{
rolesInfo: [
"<rolename>",
{ role: "<rolename>", db: "<database>" },
...
]
}
)
角色说明
角色 | 权限描述 | |
---|---|---|
read | 可以读取指定数据库中任何数据 | |
readWrite | 可以读写指定数据库中任何数据,包括创建、重命名、删除集合 | |
readAnyDatabase | 可以读取所有数据库中任何数据(除了数据库config 和local 之外) | |
readWriteAnyDatabase | 可以读写所有数据库中任何数据(除了数据库config 和local 之外) | |
userAdmin | 可以在指定数据库创建和修改用户 | |
userAdminAnyDatabase | 可以在所有数据库创建和修改用户(除了数据库config 和local 之外) | |
dbAdminAnyDatabase | 可以读取任何数据库以及对数据库进行清理、修改、压缩、获取统 计信息、执行检查等操作(除了数据库config 和local 之外) | |
dbAdmin | 可以读取指定数据库以及对数据库进行清理、修改、压缩、获取统 计信息、执行检查等操作 | |
clusterAdmin | 可以对整个集群或数据库系统进行管理操作 | |
backup | 备份MongoDB 数据最小的权限 | |
restore | 从备份文件中还原恢复MongoDB 数据(除了system.profile 集合)的 权限 | |
root | 超级账号,超级权限 | |
8.3 开启认证
8.3.1 单实例环境
-
-
查询已有用户信息
# 切换数据库use admin# 查询db.system.users.find()
-
创建用户
# 超级用户db.createUser( { user: "root", pwd: "123456", roles: ["root"] } )# 普通用户(只能对指定数据库进行操作)db.createUser( { user: "article", pwd: "article", roles: [ { role: 'readWrite', db: 'article' } ] } )
-
停止服务
# 切换数据库use admin# 关闭服务db.shutdownServer()
-
认证启动
-
参数
./mongod -f ../config/mongod.conf --auth
-
配置文件
systemLog: destination: file path: "/root/mongodb/soft/mongodb-linux-x86_64-rhel80-4.4.6/log/mongod.log" logAppend: truestorage: dbPath: "/root/mongodb/soft/mongodb-linux-x86_64-rhel80-4.4.6/data" journal: enabled: trueprocessManagement: fork: truenet: bindIp: 0.0.0.0 port: 27017security: authorization: enabled
-
-
启动认证
# 切换数据库use admin# 认证: 用户名 密码db.auth("","")
-
SpringData
认证配置spring:#数据源配置data:mongodb:# 主机地址# host: 180.76.159.126# 数据库# database: articledb# 默认端口是27017# port: 27017#帐号# username: bobo#密码# password: 123456#单机有认证的情况下,也使用字符串连接uri: mongodb://bobo:123456@180.76.159.126:27017/articledb
-
9.集群
9.1 副本集-Replica Sets
MongoDB
中的副本集(Replica Set)是一组维护相同数据集的mongod
服务。 副本集可提供冗余和高可用性,是所有生产部署的基础。 也可以说,副本集类似于有自动故障恢复功能的主从集群。通俗的讲就是用多台机器进行同一数据的异步同步,从而使多台机器拥有同一数据的多个副本,并且当主库当掉时在不需要用户干预的情况下自动切换其他备份服务器做主库。而且还可以利用副本服务器做只读服务器,实现读写分离,提高负载。
9.2 三种角色
- Primary: 主要成员,接收所有写操作,主节点
- Replicate: 副本成员,从主节点通过复制操作以维护相同的数据集,即备份数据,不可写操作,但可以读操作(但需要配置)。是默认的一种从节点类型
- Arbiter(仲裁者):不保留任何数据的副本,只具有投票选举作用。当然也可以将仲裁服务器维护为副本集的一部分,即副本成员同时也可以是仲裁者。也是一种从节点类型
9.3 分片集群-Sharded Cluster
分片(
sharding
)是一种跨多台机器分布数据的方法,MongoDB
使用分片来支持具有非常大的数据集和高吞吐量操作的部署。换句话说:分片(sharding
)是指将数据拆分,将其分散存在不同的机器上的过程。有时也用分区(partitioning
)来表示这个概念。将数据分散到不同的机器上,不需要功能强大的大型计算机就可以储存更多的数据,处理更多的负载。
具有大型数据集或高吞吐量应用程序的数据库系统可能会挑战单个服务器的容量。例如,高查询率会耗尽服务器的CPU容量。工作集大小大于系统的RAM会强调磁盘驱动器的I / O容量。
有两种解决系统增长的方法:垂直扩展和水平扩展。
垂直扩展意味着增加单个服务器的容量,例如使用更强大的CPU,添加更多RAM或增加存储空间量。可用技术的局限性可能会限制单个机器对于给定工作负载而言足够强大。此外,基于云的提供商基于可用的硬件配置具有硬性上限。结果,垂直缩放有实际的最大值。
水平扩展意味着划分系统数据集并加载多个服务器,添加其他服务器以根据需要增加容量。虽然单个机器的总体速度或容量可能不高,但每台机器处理整个工作负载的子集,可能提供比单个高速大容量服务器更高的效率。扩展部署容量只需要根据需要添加额外的服务器,这可能比单个机器的高端硬件的总体成本更低。权衡是基础架构和部署维护的复杂性增加。
MongoDB
支持通过分片进行水平扩展。
9.3 分片集群包含的组件
- 分片(存储):每个分片包含分片数据的子集。 每个分片都可以部署为副本集
mongos
(路由):mongos
充当查询路由器,在客户端应用程序和分片集群之间提供接口config servers
(“调度”的配置):配置服务器存储群集的元数据和配置设置。 从MongoDB 3.4
开
始,必须将配置服务器部署为副本集(CSRS
)
10. Change Stream
顾名思义,Change Stream即变更流,是
MongoDB
向应用发布数据变更的一种方式。即当数据库中有任何数据发生变化,应用端都可以得到通知副本节点或者分片集群支持。
10.1 原理
复制集流程
- 应用通过驱动向数据库发起写入请求
- 在同一个事务中,
MongoDB
完成oplog
和集合的修改 oplog
被其他从节点拉走- 从节点应用得到的
oplog
,同样在一个事务中完成对oplog
和集合的修改
可以看出所有的数据同步都是基于
oplog
实现的,监控它的变换就能得到所有变更的数据。变更了就是基于此实现。
10.2 SpringBoot
集成使用
配置
package com.dingwen.stusprboomon.config;
import com.dingwen.stusprboomon.listener.DocumentMessageListener;
import com.mongodb.client.model.changestream.FullDocument;
import org.bson.Document;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.messaging.ChangeStreamRequest;
import org.springframework.data.mongodb.core.messaging.DefaultMessageListenerContainer;
import org.springframework.data.mongodb.core.messaging.MessageListenerContainer;
import java.util.concurrent.*;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.match;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation;
import static org.springframework.data.mongodb.core.query.Criteria.where;
/**
* mongo config
*
* @author dingwen
* 2021.06.28 13:59
*/
@Configuration
public class MongoConfig {
@Bean
MessageListenerContainer messageListenerContainer(MongoTemplate template, DocumentMessageListener documentMessageListener) {
//核心线程数
int corePoolSize = 3;
//最大线程数
int maximumPoolSize = 6;
//超过 corePoolSize 线程数量的线程最大空闲时间
long keepAliveTime = 2;
//以秒为时间单位
TimeUnit unit = TimeUnit.SECONDS;
//创建工作队列,用于存放提交的等待执行任务
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(2);
ThreadPoolExecutor threadPoolExecutor = null;
//创建线程池
threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
new ThreadPoolExecutor.AbortPolicy());
MessageListenerContainer messageListenerContainer = new DefaultMessageListenerContainer(template, threadPoolExecutor) {
@Override
public boolean isAutoStartup() {
return true;
}
};
ChangeStreamRequest<Document> request = ChangeStreamRequest.builder(documentMessageListener)
//需要监听的集合名,不指定默认监听数据库的
// .collection("topic")
//过滤需要监听的操作类型,可以根据需求指定过滤条件
.filter(newAggregation(match(where("operationType").in("insert", "update", "replace"))))
//不设置时,文档更新时,只会发送变更字段的信息,设置UPDATE_LOOKUP会返回文档的全部信息
.fullDocumentLookup(FullDocument.UPDATE_LOOKUP)
.build();
messageListenerContainer.register(request, Document.class);
return messageListenerContainer;
}
}
监听
package com.dingwen.stusprboomon.listener;
import com.mongodb.client.model.changestream.ChangeStreamDocument;
import org.bson.Document;
import org.springframework.data.mongodb.core.messaging.Message;
import org.springframework.data.mongodb.core.messaging.MessageListener;
import org.springframework.stereotype.Component;
/**
* mongo 文档监听器
*
* @author dingwen
* 2021.06.28 13:54
*/
@Component
public class DocumentMessageListener implements MessageListener<ChangeStreamDocument<Document>,Document> {
@Override
public void onMessage(Message<ChangeStreamDocument<Document>, Document> message) {
//TODO 日志 数据同步
System.out.println("message.getProperties() = " + message.getProperties());
System.out.println("message.getBody() = " + message.getBody());
System.out.println("message.getRaw() = " + message.getRaw());
}
}
10.2.1 MongoTemplate 使用补充
/**
* 删除局部属性
*/
@Test
void deleteLocal(){
String id = "id";
Update update = new Update();
update.unset("content");
// 删除content 字段
commentDao.modify(new Query().addCriteria(Criteria.where(id)),update);
}
/**
* 局部修改
*/
@Test
void modifyLocal(){
String id = "id";
Update update = new Update();
update.set("content","test");
// 修改 content 字段 的值为 test
commentDao.modify(new Query().addCriteria(Criteria.where(id)),update);
}