MongoDB是一个文档数据库(以JSON 为数据模型),由C++语言编写,旨在为WEB应用提供可扩展的高性能数据存储解决方案。
文档来自于“JSON Document”,并非我们一般理解的 PDF,WORD 文档。
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,数据格式是BSON,一种类似JSON的二进制形式的存储格式,简称Binary JSON ,和JSON一样支持内嵌的文档对象和数组对象,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。原则上 Oracle 和 MySQL 能做的事情,MongoDB 都能做(包括 ACID 事务)。
MongoDB在数据库总排名第5,仅次于Oracle、MySQL等RDBMS,在NoSQL数据库排名首位。从诞生以来,其项目应用广度、社区活跃指数持续上升。
数据库排名网站:DB-Engines Ranking - popularity ranking of database management systems
MongoDB6.0新特性
该版本的主要功能特性包括:
Release Notes for MongoDB 6.0 — MongoDB Manual
https://help.aliyun.com/document_detail/462614.html?spm=a2c4g.312011.0.0.75b42ab8s9NasS#section-hvy-d22-stk
MongoDB vs 关系型数据库
MongoDB概念与关系型数据库(RDBMS)非常类似:
尽管这些概念大多与SQL标准定义类似,但MongoDB与传统RDBMS仍然存在不少差异,包括:
1.2 MongoDB技术优势
MongoDB基于灵活的JSON文档模型,非常适合敏捷式的快速开发。与此同时,其与生俱来的高可用、高水平扩展能力使得它在处理海量、高并发的数据应用时颇具优势。
简单直观:从错综复杂的关系模型到一目了然的对象模型
快速:最简单快速的开发方式
灵活:快速响应业务变化
原生的高可用
横向扩展能力
1.3 MongoDB应用场景
从目前阿里云 MongoDB 云数据库上的用户看,MongoDB 的应用已经渗透到各个领域:
国内外知名互联网公司都在使用MongoDB:
当前业务是否适合使用MongoDB?
没有某个业务场景必须要使用MongoDB才能解决,但使用MongoDB通常能让你以更低的成本解决问题。如果你不清楚当前业务是否适合使用MongoDB,可以通过做几道选择题来辅助决策。
只要有一项需求满足就可以考虑使用MongoDB,匹配越多,选择MongoDB越合适。
环境准备:
#如何查看linux版本
[root@hadoop01 soft]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)
下载MongoDB Community Server
下载地址:Download MongoDB Community Server | MongoDB
#下载MongoDB
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-6.0.5.tgz
tar -zxvf mongodb-linux-x86_64-rhel70-6.0.5.tgz
启动MongoDB Server
#创建dbpath和logpath
mkdir -p /mongodb/data /mongodb/log
#进入mongodb目录,启动mongodb服务
bin/mongod --port=27017 --dbpath=/mongodb/data --logpath=/mongodb/log/mongodb.log \
--bind_ip=0.0.0.0 --fork
--dbpath :指定数据文件存放目录
--logpath :指定日志文件,注意是指定文件不是目录
--logappend :使用追加的方式记录日志
--port:指定端口,默认为27017
--bind_ip:默认只监听localhost网卡
--fork: 后台启动
--auth: 开启认证模式
添加环境变量
修改/etc/profile,添加环境变量,方便执行MongoDB命令
export MONGODB_HOME=/usr/local/soft/mongodb
PATH=$PATH:$MONGODB_HOME/bin
然后执行source /etc/profile 重新加载环境变量
利用配置文件启动服务
编辑/mongodb/conf/mongo.conf文件,内容如下:
systemLog:
destination: file
path: /mongodb/log/mongod.log # log path
logAppend: true
storage:
dbPath: /mongodb/data # data directory
engine: wiredTiger #存储引擎
journal: #是否启用journal日志
enabled: true
net:
bindIp: 0.0.0.0
port: 27017 # port
processManagement:
fork: true
注意:一定要yaml格式
启动mongod
mongod -f /mongodb/conf/mongo.conf
-f 选项表示将使用配置文件启动mongodb
关闭MongoDB服务
方式1:
mongod --port=27017 --dbpath=/mongodb/data --shutdown
方式2:
进入mongosh
use admin
# 关闭MongoDB server 服务
db.shutdownServer()
mongosh是MongoDB的交互式JavaScript Shell界面,它为系统管理员提供了强大的界面,并为开发人员提供了直接测试数据库查询和操作的方法。
注意:MongoDB 6.0 移除了mongo,使用mongosh
mongosh下载地址:MongoDB Shell Download | MongoDB
#centos7 安装mongosh
wget https://downloads.mongodb.com/compass/mongodb-mongosh-1.8.0.x86_64.rpm
yum install -y mongodb-mongosh-1.8.0.x86_64.rpm
# 连接mongodb server端
mongosh --host=192.168.65.206 --port=27017
mongosh 192.168.65.206:27017
# 指定uri方式连接
mongosh mongodb://192.168.65.206:27017/test
--port:指定端口,默认为27017
--host:连接的主机地址,默认127.0.0.1
mongosh常用命令
命令 |
说明 |
show dbs | show databases |
显示数据库列表 |
use 数据库名 |
切换数据库,如果不存在创建数据库 |
db.dropDatabase() |
删除数据库 |
show collections | show tables |
显示当前数据库的集合列表 |
db.集合名.stats() |
查看集合详情 |
db.集合名.drop() |
删除集合 |
show users |
显示当前数据库的用户列表 |
show roles |
显示当前数据库的角色列表 |
show profile |
显示最近发生的操作 |
load("xxx.js") |
执行一个JavaScript脚本文件 |
exit | quit |
退出当前shell |
help |
查看mongodb支持哪些命令 |
db.help() |
查询当前数据库支持的方法 |
db.集合名.help() |
显示集合的帮助信息 |
db.version() |
查看数据库版本 |
数据库操作
#查看所有库
show dbs
# 切换到指定数据库,不存在则创建
use test
# 删除当前数据库
db.dropDatabase()
集合操作
#查看集合
show collections
#创建集合
db.createCollection("emp")
#删除集合
db.emp.drop()
创建集合语法
db.createCollection(name, options)
options参数
字段 |
类型 |
描述 |
capped |
布尔 |
(可选)如果为true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。 |
size |
数值 |
(可选)为固定集合指定一个最大值(以字节计)。 如果 capped 为 true,也需要指定该字段。 |
max |
数值 |
(可选)指定固定集合中包含文档的最大数量。 |
注意: 当集合不存在时,向集合中插入文档也会创建集合
使用用户名和密码来认证用户身份是 MongoDB 中最常用的安全认证方式。可以通过以下步骤实现:
启用认证后,客户端连接 MongoDB 服务器时需要提供用户名和密码才能成功连接。
创建管理员用户
# 设置管理员用户名密码需要切换到admin库
use admin
#创建管理员
db.createUser({user:"fox",pwd:"fox",roles:["root"]})
# 查看当前数据库所有用户信息
show users
#显示可设置权限
show roles
#显示所有用户
db.system.users.find()
常用权限
权限名 |
描述 |
read |
允许用户读取指定数据库 |
readWrite |
允许用户读写指定数据库 |
dbAdmin |
允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile |
dbOwner |
允许用户在指定数据库中执行任意操作,增、删、改、查等 |
userAdmin |
允许用户向system.users集合写入,可以在指定数据库里创建、删除和管理用户 |
clusterAdmin |
只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限 |
readAnyDatabase |
只在admin数据库中可用,赋予用户所有数据库的读权限 |
readWriteAnyDatabase |
只在admin数据库中可用,赋予用户所有数据库的读写权限 |
userAdminAnyDatabase |
只在admin数据库中可用,赋予用户所有数据库的userAdmin权限 |
dbAdminAnyDatabase |
只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限 |
root |
只在admin数据库中可用。超级账号,超级权限 |
重新赋予用户操作权限
db.grantRolesToUser( "fox" , [
{ role: "clusterAdmin", db: "admin" } ,
{ role: "userAdminAnyDatabase", db: "admin"},
{ role: "readWriteAnyDatabase", db: "admin"}
])
删除用户
db.dropUser("fox")
#删除当前数据库所有用户
db.dropAllUser()
用户认证,返回1表示认证成功
创建应用数据库用户
use appdb
db.createUser({user:"appdb",pwd:"fox",roles:["dbOwner"]})
MongoDB启用鉴权
默认情况下,MongoDB不会启用鉴权,以鉴权模式启动MongoDB
mongod -f /mongodb/conf/mongo.conf --auth
启用鉴权之后,连接MongoDB的相关操作都需要提供身份认证。
mongosh 192.168.65.206:27017 -u fox -p fox --authenticationDatabase=admin
https://hub.docker.com/_/mongo?tab=description&page=3
#拉取mongo镜像
docker pull mongo:6.0.5
#运行mongo镜像
docker run --name mongo-server -p 29017:27017 \
-e MONGO_INITDB_ROOT_USERNAME=fox \
-e MONGO_INITDB_ROOT_PASSWORD=fox \
-d mongo:6.0.5 --wiredTigerCacheSizeGB 1
默认情况下,Mongo会将wiredTigerCacheSizeGB设置为与主机总内存成比例的值,而不考虑你可能对容器施加的内存限制。
MONGO_INITDB_ROOT_USERNAME和MONGO_INITDB_ROOT_PASSWORD都存在就会启用身份认证(mongod --auth)
利用mongosh建立连接
#远程连接
mongosh ip:29017 -u fox -p fox
GUI工具
官方GUI:COMPASS
MongoDB图形化管理工具(GUI),能够帮助您在不需要知道MongoDB查询语法的前提下,便利地分析和理解您的数据库模式,并且帮助您可视化地构建查询。
下载地址:MongoDB Compass | MongoDB
Robo 3T(免费)
下载地址:Robo 3T | Free, open-source MongoDB GUI (formerly Robomongo)
Studio 3T(收费,试用30天)
下载地址:Download Studio 3T for MongoDB | Windows, macOS & Linux
MongoDB Database Tools
下载地址:Download MongoDB Command Line Database Tools | MongoDB
文件名称 |
作用 |
mongostat |
数据库性能监控工具 |
mongotop |
热点表监控工具 |
mongodump |
数据库逻辑备份工具 |
mongorestore |
数据库逻辑恢复工具 |
mongoexport |
数据导出工具 |
mongoimport |
数据导入工具 |
bsondump |
BSON格式转换工具 |
mongofiles |
GridFS文件工具 |
SQL to MongoDB Mapping Chart :SQL to MongoDB Mapping Chart — MongoDB Manual
MongoDB提供了以下方法将文档插入到集合中:
db.collection.insertOne(
,
{
writeConcern:
}
)
设置 writeConcern 参数的示例
db.emps.insertOne(
{ name: "fox", age: 35},
{
writeConcern: { w: "majority", j: true, wtimeout: 5000 }
}
)
writeConcern 是 MongoDB 中用来控制写入确认的选项。以下是 writeConcern 参数的一些常见选项:
db.collection.insertMany(
[ , , ... ],
{
writeConcern: ,
ordered:
}
)
编辑脚本book.js
var tags = ["nosql","mongodb","document","developer","popular"];
var types = ["technology","sociality","travel","novel","literature"];
var books=[];
for(var i=0;i<50;i++){
var typeIdx = Math.floor(Math.random()*types.length);
var tagIdx = Math.floor(Math.random()*tags.length);
var favCount = Math.floor(Math.random()*100);
var book = {
title: "book-"+i,
type: types[typeIdx],
tag: tags[tagIdx],
favCount: favCount,
author: "xxx"+i
};
books.push(book)
}
db.books.insertMany(books);
进入mongosh,执行
load("books.js")
语法格式如下:
db.collection.find(query, projection)
如果查询返回的条目数量较多,mongosh则会自动实现分批显示。默认情况下每次只显示20条,可以输入it命令读取下一批。
语法格式如下:
db.collection.findOne(query, projection)
如果你需要以易读的方式来读取数据,可以使用pretty)方法,语法格式如下:
db.collection.find().pretty()
注意:pretty()方法以格式化的方式来显示所有文档
查询条件对照表
SQL |
MQL |
a = 1 |
{a: 1} |
a <> 1 |
{a: {$ne: 1}} |
a > 1 |
{a: {$gt: 1}} |
a >= 1 |
{a: {$gte: 1}} |
a < 1 |
{a: {$lt: 1}} |
a <= 1 |
{a: {$lte: 1}} |
查询逻辑对照表
SQL |
MQL |
a = 1 AND b = 1 |
{a: 1, b: 1}或{$and: [{a: 1}, {b: 1}]} |
a = 1 OR b = 1 |
{$or: [{a: 1}, {b: 1}]} |
a IS NULL |
{a: {$exists: false}} |
a IN (1, 2, 3) |
{a: {$in: [1, 2, 3]}} |
查询逻辑运算符
#查询带有nosql标签的book文档:
db.books.find({tag:"nosql"})
#按照id查询单个book文档:
db.books.find({_id:ObjectId("61caa09ee0782536660494d9")})
#查询分类为“travel”、收藏数超过60个的book文档:
db.books.find({type:"travel",favCount:{$gt:60}})
正则表达式匹配查询
MongoDB 使用 $regex 操作符来设置匹配字符串的正则表达式。
//使用正则表达式查找type包含 so 字符串的book
db.books.find({type:{$regex:"so"}})
//或者
db.books.find({type:/so/})
排序
在 MongoDB 中使用 sort() 方法对数据进行排序
#指定按收藏数(favCount)降序返回
db.books.find({type:"travel"}).sort({favCount:-1})
#指定按收藏数(favCount)降序返回 db.books.find({type:"travel"}).sort({favCount:-1})
分页
skip用于指定跳过记录数,limit则用于限定返回结果数量。可以在执行find命令的同时指定skip、limit参数,以此实现分页的功能。
比如,假定每页大小为8条,查询第3页的book文档:
db.books.find().skip(16).limit(8)
处理分页问题 – 巧分页
数据量大的时候,应该避免使用skip/limit形式的分页。
替代方案:使用查询条件+唯一排序条件;
例如:
第一页:db.books.find({}).sort({_id: 1}).limit(10);
第二页:db.books.find({_id: {$gt: <第一页最后一个_id>}}).sort({_id: 1}).limit(10);
第三页:db.books.find({_id: {$gt: <第二页最后一个_id>}}).sort({_id: 1}).limit(10);
处理分页问题 – 避免使用 count
尽可能不要计算总页数,特别是数据量大和查询条件不能完整命中索引时。
考虑以下场景:假设集合总共有 1000w 条数据,在没有索引的情况下考虑以下查询:
db.coll.find({x: 100}).limit(50); db.coll.count({x: 100});
MongoDB提供了以下方法来更新集合中的文档:
更新操作符
操作符 |
格式 |
描述 |
$set |
{$set:{field:value}} |
指定一个键并更新值,若键不存在则创建 |
$unset |
{$unset : {field : 1 }} |
删除一个键 |
$inc |
{$inc : {field : value } } |
对数值类型进行增减 |
$rename |
{$rename : {old_field_name : new_field_name } } |
修改字段名称 |
$push |
{ $push : {field : value } } |
将数值追加到数组中,若数组不存在则会进行初始化 |
$pushAll |
{$pushAll : {field : value_array }} |
追加多个值到一个数组字段内 |
$pull |
{$pull : {field : _value } } |
从数组中删除指定的元素 |
$addToSet |
{$addToSet : {field : value } } |
添加元素到数组中,具有排重功能 |
$pop |
{$pop : {field : 1 }} |
删除数组的第一个或最后一个元素 |
updateOne语法如下:
db.collection.updateOne(
,
,
{
upsert: ,
writeConcern: ,
collation: ,
arrayFilters: [ , ... ],
hint: // Available starting in MongoDB 4.2.1
}
)
db.collection.updateOne()方法的参数含义如下:
注意,除了filter和update参数外,其他参数都是可选的。
某个book文档被收藏了,则需要将该文档的favCount字段自增
db.books.updateOne({_id:ObjectId("642e62ec933c0dca8f8e9f60")},{$inc:{favCount:1}})
upsert是一种特殊的更新,其表现为如果目标文档不存在,则执行插入命令。
db.books.updateOne(
{title:"my book"},
{$set:{tags:["nosql","mongodb"],type:"none",author:"fox"}},
{upsert:true}
)
updateMany更新与集合的指定筛选器匹配的所有文档
将分类为“novel”的文档的增加发布时间(publishedDate)
db.books.updateMany({type:"novel"},{$set:{publishedDate:new Date()}})
findAndModify
findAndModify兼容了查询和修改指定文档的功能,findAndModify只能更新单个文档
//将某个book文档的收藏数(favCount)加1
db.books.findAndModify({
query:{_id:ObjectId("6457a39c817728350ec83b9d")},
update:{$inc:{favCount:1}}
})
该操作会返回符合查询条件的文档数据,并完成对文档的修改。
默认情况下,findAndModify会返回修改前的“旧”数据。如果希望返回修改后的数据,则可以指定new选项
db.books.findAndModify({
query:{_id:ObjectId("6457a39c817728350ec83b9d")},
update:{$inc:{favCount:1}},
new: true
})
与findAndModify语义相近的命令如下:
官方推荐使用 deleteOne() 和 deleteMany() 方法删除文档,语法格式如下:
db.books.deleteOne ({ type:"novel" }) //删除 type等于novel 的一个文档
db.books.deleteMany ({}) //删除集合下全部文档
db.books.deleteMany ({ type:"novel" }) //删除 type等于 novel 的全部文档
注意:remove、deleteMany命令需要对查询范围内的文档逐个删除,如果希望删除整个集合,则使用drop命令会更加高效
findOneAndDelete
deleteOne命令在删除文档后只会返回确认性的信息,如果希望获得被删除的文档,则可以使用findOneAndDelete命令
db.books.findOneAndDelete({type:"novel"})
除了在结果中返回删除文档,findOneAndDelete命令还允许定义“删除的顺序”,即按照指定顺序删除找到的第一个文档。利用这个特性,findOneAndDelete可以实现队列的先进先出。
db.books.findOneAndDelete({type:"novel"},{sort:{favCount:1}})
bulkwrite()方法提供了执行批量插入、更新和删除操作的能力。
bulkWrite()支持以下写操作:
每个写操作都作为数组中的文档传递给bulkWrite()。
db.pizzas.insertMany( [
{ _id: 0, type: "pepperoni", size: "small", price: 4 },
{ _id: 1, type: "cheese", size: "medium", price: 7 },
{ _id: 2, type: "vegan", size: "large", price: 8 }
] )
db.pizzas.bulkWrite( [
{ insertOne: { document: { _id: 3, type: "beef", size: "medium", price: 6 } } },
{ insertOne: { document: { _id: 4, type: "sausage", size: "large", price: 10 } } },
{ updateOne: {
filter: { type: "cheese" },
update: { $set: { price: 8 } }
} },
{ deleteOne: { filter: { type: "pepperoni"} } },
{ replaceOne: {
filter: { type: "vegan" },
replacement: { type: "tofu", size: "small", price: 4 }
} }
] )
MongoDB为什么会使用BSON?
JSON是当今非常通用的一种跨语言Web数据交互格式,属于ECMAScript标准规范的一个子集。JSON(JavaScript Object Notation, JS对象简谱)即JavaScript对象表示法,它是JavaScript对象的一种文本表现形式。
作为一种轻量级的数据交换格式,JSON的可读性非常好,而且非常便于系统生成和解析,这些优势也让它逐渐取代了XML标准在Web领域的地位,当今许多流行的Web应用开发框架,如SpringBoot都选择了JSON作为默认的数据编/解码格式。
JSON只定义了6种数据类型:
大多数情况下,使用JSON作为数据交互格式已经是理想的选择,但是JSON基于文本的解析效率并不是最好的,在某些场景下往往会考虑选择更合适的编/解码格式,一些做法如:
BSON由10gen团队设计并开源,目前主要用于MongoDB数据库。BSON(Binary JSON)是二进制版本的JSON,其在性能方面有更优的表现。BSON在许多方面和JSON保持一致,其同样也支持内嵌的文档对象和数组结构。二者最大的区别在于JSON是基于文本的,而BSON则是二进制(字节流)编/解码的形式。在空间的使用上,BSON相比JSON并没有明显的优势。
MongoDB在文档存储、命令协议上都采用了BSON作为编/解码格式,主要具有如下优势:
BSON的数据类型
MongoDB中,一个BSON文档最大大小为16M,文档嵌套的级别不超过100
BSON Types — MongoDB Manual
Type |
Number |
Alias |
Notes |
Double |
1 |
"double" |
|
String |
2 |
"string" |
|
Object |
3 |
"object" |
|
Array |
4 |
"array" |
|
Binary data |
5 |
"binData" |
二进制数据 |
Undefined |
6 |
"undefined" |
Deprecated. |
ObjectId |
7 |
"objectId" |
对象ID,用于创建文档ID |
Boolean |
8 |
"bool" |
|
Date |
9 |
"date" |
|
Null |
10 |
"null" |
|
Regular Expression |
11 |
"regex" |
正则表达式 |
DBPointer |
12 |
"dbPointer" |
Deprecated. |
JavaScript |
13 |
"javascript" |
|
Symbol |
14 |
"symbol" |
Deprecated. |
JavaScript code with scope |
15 |
"javascriptWithScope" |
Deprecated in MongoDB 4.4. |
32-bit integer |
16 |
"int" |
|
Timestamp |
17 |
"timestamp" |
|
64-bit integer |
18 |
"long" |
|
Decimal128 |
19 |
"decimal" |
New in version 3.4. |
Min key |
-1 |
"minKey" |
表示一个最小值 |
Max key |
127 |
"maxKey" |
表示一个最大值 |
$type操作符
$type操作符基于BSON类型来检索集合中匹配的数据类型,并返回结果。
db.books.find({"title" : {$type : 2}}) //或者 db.books.find({"title" : {$type : "string"}})
MongoDB的日期类型使用UTC(Coordinated Universal Time,即世界协调时)进行存储,也就是+0时区的时间。
db.dates.insertMany([{data1:Date()},{data2:new Date()},{data3:ISODate()}]) db.dates.find().pretty()
使用new Date与ISODate最终都会生成ISODate类型的字段(对应于UTC时间)
MongoDB集合中所有的文档都有一个唯一的_id字段,作为集合的主键。在默认情况下,_id字段使用ObjectId类型,采用16进制编码形式,共12个字节。
为了避免文档的_id字段出现重复,ObjectId被定义为3个部分:
大多数客户端驱动都会自行生成这个字段,比如MongoDB Java Driver会根据插入的文档是否包含_id字段来自动补充ObjectId对象。这样做不但提高了离散性,还可以降低MongoDB服务器端的计算压力。在ObjectId的组成中,5字节的随机数并没有明确定义,客户端可以采用机器号、进程号来实现:
属性/方法 |
描述 |
str |
返回对象的十六进制字符串表示。 |
ObjectId.getTimestamp() |
将对象的时间戳部分作为日期返回。 |
ObjectId.toString() |
以字符串文字“”的形式返回 JavaScript 表示ObjectId(...)。 |
ObjectId.valueOf() |
将对象的表示形式返回为十六进制字符串。返回的字符串是str属性。 |
生成一个新的 ObjectId
x = ObjectId()
内嵌文档
一个文档中可以包含作者的信息,包括作者名称、性别、家乡所在地,一个显著的优点是,当我们查询book文档的信息时,作者的信息也会一并返回。
db.books.insert({
title: "撒哈拉的故事",
author: {
name:"三毛",
gender:"女",
hometown:"重庆"
}
})
查询三毛的作品
db.books.find({"author.name":"三毛"})
修改三毛的家乡所在地
db.books.updateOne({"author.name":"三毛"},{$set:{"author.hometown":"重庆/台湾"}})
数组
除了作者信息,文档中还包含了若干个标签,这些标签可以用来表示文档所包含的一些特征,如豆瓣读书中的标签(tag)
增加tags标签
db.books.updateOne({"author.name":"三毛"},{$set:{tags:["旅行","随笔","散文","爱情","文学"]}})
查询数组元素
# 会查询到所有的tags
db.books.find({"author.name":"三毛"},{title:1,tags:1})
#利用$slice获取最后一个tag
db.books.find({"author.name":"三毛"},{title:1,tags:{$slice:-1}})
$silice是一个查询操作符,用于指定数组的切片方式
数组末尾追加元素,可以使用$push操作符
db.books.updateOne({"author.name":"三毛"},{$push:{tags:"猎奇"}})
$push操作符可以配合其他操作符,一起实现不同的数组修改操作,比如和$each操作符配
合可以用于添加多个元素
db.books.updateOne({"author.name":"三毛"},{$push:{tags:{$each:["伤感","想象力"]}}})
如果加上$slice操作符,那么只会保留经过切片后的元素
db.books.updateOne({"author.name":"三毛"},{$push:{tags:{$each:["伤感","想象力"],$slice:-3}}})
根据元素查询
#会查出所有包含伤感的文档
db.books.find({tags:"伤感"})
# 会查出所有同时包含"伤感","想象力"的文档
db.books.find({tags:{$all:["伤感","想象力"]}})
嵌套型的数组
数组元素可以是基本类型,也可以是内嵌的文档结构
{
tags:[
{tagKey:xxx,tagValue:xxxx},
{tagKey:xxx,tagValue:xxxx}
]
}
这种结构非常灵活,一个很适合的场景就是商品的多属性表示
一个商品可以同时包含多个维度的属性,比如尺码、颜色、风格等,使用文档可以表示为:
db.goods.insertMany([{
name:"羽绒服",
tags:[
{tagKey:"size",tagValue:["M","L","XL","XXL","XXXL"]},
{tagKey:"color",tagValue:["黑色","宝蓝"]},
{tagKey:"style",tagValue:"韩风"}
]
},{
name:"羊毛衫",
tags:[
{tagKey:"size",tagValue:["L","XL","XXL"]},
{tagKey:"color",tagValue:["蓝色","杏色"]},
{tagKey:"style",tagValue:"韩风"}
]
}])
以上的设计是一种常见的多值属性的做法,当我们需要根据属性进行检索时,需要用到$elementMatch操作符:
#筛选出color=黑色的商品信息
db.goods.find({
tags:{
$elemMatch:{tagKey:"color",tagValue:"黑色"}
}
})
如果进行组合式的条件检索,则可以使用多个$elemMatch操作符:
# 筛选出color=蓝色,并且size=XL的商品信息
db.goods.find({
tags:{
$all:[
{$elemMatch:{tagKey:"color",tagValue:"黑色"}},
{$elemMatch:{tagKey:"size",tagValue:"XL"}}
]
}
})
Capped Collections — MongoDB Manual
固定集合(capped collection)是一种限定大小的集合,其中capped是覆盖、限额的意思。跟普通的集合相比,数据在写入这种集合时遵循FIFO原则。可以将这种集合想象为一个环状的队列,新文档在写入时会被插入队列的末尾,如果队列已满,那么之前的文档就会被新写入的文档所覆盖。通过固定集合的大小,我们可以保证数据库只会存储“限额”的数据,超过该限额的旧数据都会被丢弃。
使用示例
创建固定集合
db.createCollection("logs",{capped:true,size:4096,max:10})
这两个参数会同时对集合的上限产生影响。也就是说,只要任一条件达到阈值都会认为集合已经写满。其中size是必选的,而max则是可选的。
可以使用collection.stats命令查看文档的占用空间
db.logs.stats()
将普通集合转换为固定集合
db.runCommand({"convertToCapped": "mycoll", size: 100000})
测试
尝试在这个集合中插入15条数据,再查询会发现,由于文档数量上限被设定为10条,前面插入的5条数据已经被覆盖了
for(var i=0;i<15;i++){
db.logs.insert({t:"row-"+i})
}
适用场景
固定集合很适合用来存储一些“临时态”的数据。“临时态”意味着数据在一定程度上可以被丢弃。同时,用户还应该更关注最新的数据,随着时间的推移,数据的重要性逐渐降低,直至被淘汰处理。
一些适用的场景如下:
在股票实时系统中,大家往往最关心股票价格的变动。而应用系统中也需要根据这些实时的变化数据来分析当前的行情。倘若将股票的价格变化看作是一个事件,而股票交易所则是价格变动事件的“发布者”,股票APP、应用系统则是事件的“消费者”。这样,我们就可以将股票价格的发布、通知抽象为一种数据的消费行为,此时往往需要一个消息队列来实现该需求。
结合业务场景: 利用固定集合实现存储股票价格变动信息的消息队列
db.createCollection("stock_queue",{capped:true,size:10485760})
{
timestamped:new Date(),
stock: "MongoDB Inc",
price: 20.33
}
为了能支持按时间条件进行快速的检索,比如查询某个时间点之后的数据,可以为timestamp添加索引
db.stock_queue.createIndex({timestamped:1})
模拟股票的实时变动
function pushEvent(){
while(true){
db.stock_queue.insert({
timestamped:new Date(),
stock: "MongoDB Inc",
price: 100*Math.random(1000)
});
print("publish stock changed");
sleep(1000);
}
}
执行pushEvent函数,此时客户端会每隔1秒向stock_queue中写入一条股票信息
pushEvent()
对于消费方来说,更关心的是最新数据,同时还应该保持持续进行“拉取”,以便知晓实时发生的变化。根据这样的逻辑,可以实现一个listen函数
function listen(){
var cursor = db.stock_queue.find({timestamped:{$gte:new Date()}}).tailable();
while(true){
if(cursor.hasNext()){
print(JSON.stringify(cursor.next(),null,2));
}
sleep(1000);
}
}
find操作的查询条件被指定为仅查询比当前时间更新的数据,而由于采用了读取游标的方式,因此游标在获取不到数据时并不会被关闭,这种行为非常类似于Linux中的tail-f命令。在一个循环中会定时检查是否有新的数据产生,一旦发现新的数据(cursor.hasNext()=true),则直接将数据打印到控制台。
执行这个监听函数,就可以看到实时发布的股票信息
listen()
https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.nosql.mongodb.repositories
Spring Data MongoDB :: Spring Data MongoDB
1)引入依赖
org.springframework.boot
spring-boot-starter-data-mongodb
2)配置yml
spring:
data:
mongodb:
uri: mongodb://fox:[email protected]:27017/test?authSource=admin
#uri等同于下面的配置
#database: test
#host: 192.168.65.174
#port: 27017
#username: fox
#password: fox
#authentication-database: admin
连接配置参考文档:Connection Strings — MongoDB Manual
3)使用时注入mongoTemplate
@Autowired
MongoTemplate mongoTemplate;
4)集合操作
@Test
public void testCollection(){
boolean exists = mongoTemplate.collectionExists("emp");
if (exists) {
//删除集合
mongoTemplate.dropCollection("emp");
}
//创建集合
mongoTemplate.createCollection("emp");
}
相关注解
创建实体
@Document("emp") //对应emp集合中的一个文档
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
@Id //映射文档中的_id
private Integer id;
@Field("username")
private String name;
@Field
private int age;
@Field
private Double salary;
@Field
private Date entryDay;
}
添加文档
insert方法返回值是新增的Document对象,里面包含了新增后_id的值。如果集合不存在会自动创建集合。通过Spring Data MongoDB还会给集合中多加一个_class的属性,存储新增时Document对应Java中类的全限定路径。这么做为了查询时能把Document转换为Java类型。
@Test
public void testInsert(){
Employee employee = new Employee(1, "小明", 30,10000.00, new Date());
//添加文档
// sava: _id存在时更新数据
//mongoTemplate.save(employee);
// insert: _id存在抛出异常 支持批量操作
mongoTemplate.insert(employee);
List list = Arrays.asList(
new Employee(2, "张三", 21,5000.00, new Date()),
new Employee(3, "李四", 26,8000.00, new Date()),
new Employee(4, "王五",22, 8000.00, new Date()),
new Employee(5, "张龙",28, 6000.00, new Date()),
new Employee(6, "赵虎",24, 7000.00, new Date()),
new Employee(7, "赵六",28, 12000.00, new Date()));
//插入多条数据
mongoTemplate.insert(list,Employee.class);
}
查询文档
Criteria是标准查询的接口,可以引用静态的Criteria.where的把多个条件组合在一起,就可以轻松地将多个方法标准和查询连接起来,方便我们操作查询语句。
@Test
public void testFind(){
System.out.println("==========查询所有文档===========");
//查询所有文档
List list = mongoTemplate.findAll(Employee.class);
list.forEach(System.out::println);
System.out.println("==========根据_id查询===========");
//根据_id查询
Employee e = mongoTemplate.findById(1, Employee.class);
System.out.println(e);
System.out.println("==========findOne返回第一个文档===========");
//如果查询结果是多个,返回其中第一个文档对象
Employee one = mongoTemplate.findOne(new Query(), Employee.class);
System.out.println(one);
System.out.println("==========条件查询===========");
//new Query() 表示没有条件
//查询薪资大于等于8000的员工
//Query query = new Query(Criteria.where("salary").gte(8000));
//查询薪资大于4000小于10000的员工
//Query query = new Query(Criteria.where("salary").gt(4000).lt(10000));
//正则查询(模糊查询) java中正则不需要有//
//Query query = new Query(Criteria.where("name").regex("张"));
//and or 多条件查询
Criteria criteria = new Criteria();
//and 查询年龄大于25&薪资大于8000的员工
//criteria.andOperator(Criteria.where("age").gt(25),Criteria.where("salary").gt(8000));
//or 查询姓名是张三或者薪资大于8000的员工
criteria.orOperator(Criteria.where("name").is("张三"),Criteria.where("salary").gt(5000));
Query query = new Query(criteria);
//sort排序
//query.with(Sort.by(Sort.Order.desc("salary")));
//skip limit 分页 skip用于指定跳过记录数,limit则用于限定返回结果数量。
query.with(Sort.by(Sort.Order.desc("salary")))
.skip(0) //指定跳过记录数
.limit(4); //每页显示记录数
//查询结果
List employees = mongoTemplate.find(
query, Employee.class);
employees.forEach(System.out::println);
}
public void testFindByJson() {
//使用json字符串方式查询
//等值查询
//String json = "{name:'张三'}";
//多条件查询
String json = "{$or:[{age:{$gt:25}},{salary:{$gte:8000}}]}";
Query query = new BasicQuery(json);
//查询结果
List employees = mongoTemplate.find(
query, Employee.class);
employees.forEach(System.out::println);
}
更新文档
在Mongodb中无论是使用客户端API还是使用Spring Data,更新返回结果一定是受行数影响。如果更新后的结果和更新前的结果是相同,返回0。
@Test
public void testUpdate(){
//query设置查询条件
Query query = new Query(Criteria.where("salary").gte(15000));
System.out.println("==========更新前===========");
List employees = mongoTemplate.find(query, Employee.class);
employees.forEach(System.out::println);
Update update = new Update();
//设置更新属性
update.set("salary",13000);
//updateFirst() 只更新满足条件的第一条记录
//UpdateResult updateResult = mongoTemplate.updateFirst(query, update, Employee.class);
//updateMulti() 更新所有满足条件的记录
//UpdateResult updateResult = mongoTemplate.updateMulti(query, update, Employee.class);
//upsert() 没有符合条件的记录则插入数据
//update.setOnInsert("id",11); //指定_id
UpdateResult updateResult = mongoTemplate.upsert(query, update, Employee.class);
//返回修改的记录数
System.out.println(updateResult.getModifiedCount());
System.out.println("==========更新后===========");
employees = mongoTemplate.find(query, Employee.class);
employees.forEach(System.out::println);
}
删除文档
@Test
public void testDelete(){
//删除所有文档
//mongoTemplate.remove(new Query(),Employee.class);
//条件删除
Query query = new Query(Criteria.where("salary").gte(10000));
mongoTemplate.remove(query,Employee.class);
}
@Configuration
public class TulingMongoConfig {
/**
* 定制TypeMapper去掉_class属性
* @param mongoDatabaseFactory
* @param context
* @param conversions
* @return
*/
@Bean
MappingMongoConverter mappingMongoConverter(
MongoDatabaseFactory mongoDatabaseFactory,
MongoMappingContext context, MongoCustomConversions conversions){
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDatabaseFactory);
MappingMongoConverter mappingMongoConverter =
new MappingMongoConverter(dbRefResolver,context);
mappingMongoConverter.setCustomConversions(conversions);
//构造DefaultMongoTypeMapper,将typeKey设置为空值
mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
return mappingMongoConverter;
}
}