MongoDB
简介
什么是MongoDB
?
一种数据库,而且是非关系型数据库。
MongoDB
的优点
- 可以快速开发
web
型应用,因为灵活,不用像关系型数据库一样需要建表 MongoDB
存储的是文档(document
),文档内存储的是类似json
的结构,所谓json
就是字符串数组
MongoDB
常见命令
MongoDB
的安装
我这里使用的
docker
的方法,注意,这里使用4版本,使用其他版本会出问题,暂时未解决。
# 运行并下载最新的mongodb镜像
docker run -itd --name mongo -p 27017:27017 mongo:4 --auth
# 使用admin账户进行登录
docker exec -it mongo mongo admin
# 创建一个admin账号,密码为 admin
db.createUser({ user:'admin',pwd:'admin',roles:[ { role:'userAdminAnyDatabase', db: 'admin'},"readWriteAnyDatabase"]});
# 尝试使用新创建的用户进行连接
db.auth('admin', 'admin')
# 开启服务器:
net start MongoDB
# 关闭服务器:
net stop MongoDB
# 打开客户端: 在cmd窗口输入便可以连接上数据库
mongo
# 查看数据库信息
> show dbs;
admin 0.000GB
config 0.000GB
local 0.000GB
# 查看当前数据库有什么集合
> show collections;
# 查看所在数据库
> db;
# 进入数据库
use ljq;
MongoDB
的数据库分类
- 数据库(
database
):用来存储集合的,而且数据库也有分大小。 - 集合(
collection
):集合类似于数组,用于存放文档的。 - 文档(
document
): 文档是MongoDB
数据库中最小的单位,我们要操作的目标就是文档。
MongoDB
关系: 数据库(database
) > 集合(collection
)> 文档(document
)
值得注意的是: 在MongoDB
中不需要自己创建数据库和集合,便可以直接创建文档,其实就是在创建文档的同时,会将数据库和集合创建了。
mongodb
数据库的CRUD操作
核心: 增: insert()
删: remove()
查: find()
改: update()
增加
表示向 集合名为
ljq
插入了文档, 文档内容是对象。 集合名是自己创建的,想用什么名称就用什么。
# 表示增加一个或多个文档
db.集合名.insert(document)
db.ljq.insert({name: "孙悟空", age: 28, address:"花果山"});
db.ljq.insert([
{name:"猪八戒", age: 38, address:"高老庄"},
{name: "沙和尚", age: 45, address:"流沙河"}
]);
# 表示插入一个文档,回调函数callback是可选的
db.集合名.insertOne(document,[callback])
# 表示向集合名为piyou的 插入了一个文档。
db.ljq.insertOne({name:"唐僧", age: 18, address: "女儿国"});
# 表示插入多个文档
db.集合名.insertMany(doucment, [callback]);
# 可以看到,用法是和insert是相差不多的。数组里面有对象,对象即是文档。
db.ljq.insertMany([
{name:"白骨精", age:20,address:"白骨洞"},
{name:"蜘蛛精", age: 24, address:"蜘蛛洞"}
]);
查找
find()
返回的是数组,数组内存放着文档,findOne()
返回的就是一个文档,findMany()
返回的也是数组内存放着文档的形式。find()
的返回值还可以调用count()
, 用来获取符合条件的数量
# 查询一个或多个文档 condition是查询条件
db.集合名.find(condition);
# 查询一个文档
db.集合名.findOne(condition);
# 查询多个文档
db.集合名.findMany(condition);
# 这条语句表示的是查询age是18的文档。 值得注意的是,条件也是写在了对象里面,
# 也是因为传入的值需要是json语句
db.ljq.find({age:18});
# 使用count()来计数
db.集合名.find(condition).count();
db.ljq.find().count();
修改
# 修改一个或多个文档 condition: 查询的条件 newObject: 需要修改的语句
db.集合名.update(condition,newObject);
# 修改一个文档
db.集合名.uodateOne(condition,newObject);
# 修改多个文档
db.集合名.updateMany(condition, newObject);
首先需要注意的是,在使用update()时,需要一个修改操作符
$set # 表示需要设置指定的属性
$unset # 表示需要删除指定的属性
$push # 表示给数组添加一个新元素,因为文档内也会有数组,数组便会有数组元素
$addToset # 表示给数组添加一个新元素,和push的区别是,如果出现同名的数组元素,则不会再添加
$gt # 大于
$gte # 大于等于
$lt # 小于
$lte # 小于等于
$or [{条件一,条件二}] # 表示或的意思,符合条件一或者条件二
$inc # 表示自增,用在在原来数据的基础上对数据加减,可用于加薪减薪的操作
所以不能像下面这条语句这样使用,这样使用的话,会使用{age:18}
覆盖掉{name:“猪八戒”…}
这整条语句。
db.ljq.update({name:"猪八戒"}, {age:39});
> db.ljq.update({name:"猪八戒"}, {age:39});
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.ljq.find({});
{ "_id" : ObjectId("633adb3cc2973e9852d04dca"), "name" : "孙悟空", "age" : 28, "address" : "花果山" }
{ "_id" : ObjectId("633adb72c2973e9852d04dcb"), "age" : 39 }
{ "_id" : ObjectId("633adb72c2973e9852d04dcc"), "name" : "沙和尚", "age" : 45, "address" : "流沙河" }
{ "_id" : ObjectId("633adbc5c2973e9852d04dcd"), "name" : "唐僧", "age" : 18, "address" : "女儿国" }
{ "_id" : ObjectId("633adbf1c2973e9852d04dce"), "name" : "白骨精", "age" : 20, "address" : "白骨洞" }
{ "_id" : ObjectId("633adbf1c2973e9852d04dcf"), "name" : "蜘蛛精", "age" : 24, "address" : "蜘蛛洞" }
正确的修改
# 表示根据条件{name:"孙悟空"}, 找到了孙悟空的这个文档, 使用了$set(修改指定属性)
db.ljq.update({name:"孙悟空"}, {$set:{age:29}});
> db.ljq.update({name:"孙悟空"}, {$set:{age:29}});
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.ljq.find({});
{ "_id" : ObjectId("633adb3cc2973e9852d04dca"), "name" : "孙悟空", "age" : 29, "address" : "花果山" }
{ "_id" : ObjectId("633adb72c2973e9852d04dcb"), "age" : 39 }
{ "_id" : ObjectId("633adb72c2973e9852d04dcc"), "name" : "沙和尚", "age" : 45, "address" : "流沙河" }
{ "_id" : ObjectId("633adbc5c2973e9852d04dcd"), "name" : "唐僧", "age" : 18, "address" : "女儿国" }
{ "_id" : ObjectId("633adbf1c2973e9852d04dce"), "name" : "白骨精", "age" : 20, "address" : "白骨洞" }
{ "_id" : ObjectId("633adbf1c2973e9852d04dcf"), "name" : "蜘蛛精", "age" : 24, "address" : "蜘蛛洞" }
其他用法
# 表示使用 $unset(删除指定属性), 将唐僧的address的属性值给删掉了
db.ljq.update({name:"唐僧"}, {$unset:{address: 1}});
> db.ljq.update({name:"唐僧"}, {$unset:{address: 1}});
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.ljq.find({});
{ "_id" : ObjectId("633adb3cc2973e9852d04dca"), "name" : "孙悟空", "age" : 29, "address" : "花果山" }
{ "_id" : ObjectId("633adb72c2973e9852d04dcb"), "age" : 39 }
{ "_id" : ObjectId("633adb72c2973e9852d04dcc"), "name" : "沙和尚", "age" : 45, "address" : "流沙河" }
{ "_id" : ObjectId("633adbc5c2973e9852d04dcd"), "name" : "唐僧", "age" : 18 }
{ "_id" : ObjectId("633adbf1c2973e9852d04dce"), "name" : "白骨精", "age" : 20, "address" : "白骨洞" }
{ "_id" : ObjectId("633adbf1c2973e9852d04dcf"), "name" : "蜘蛛精", "age" : 24, "address" : "蜘蛛洞" }
删除
# 删除符合条件的一个或多个文档
db.集合名.remove(condition)
# 删除符合条件的一个文档
db.集合名.deleteOne(condition)
# 删除符合条件的多个文档
db.集合名.deleteMany(condition)
# 表示删除了符合 age为39 这个条件的一个或多个文档。 也就是删掉了刚刚那个猪八戒的那条语句
db.ljq.remove({age: 39});
> db.ljq.remove({age: 39});
WriteResult({ "nRemoved" : 1 })
> db.ljq.find({});
{ "_id" : ObjectId("633adb3cc2973e9852d04dca"), "name" : "孙悟空", "age" : 29, "address" : "花果山" }
{ "_id" : ObjectId("633adb72c2973e9852d04dcc"), "name" : "沙和尚", "age" : 45, "address" : "流沙河" }
{ "_id" : ObjectId("633adbc5c2973e9852d04dcd"), "name" : "唐僧", "age" : 18 }
{ "_id" : ObjectId("633adbf1c2973e9852d04dce"), "name" : "白骨精", "age" : 20, "address" : "白骨洞" }
{ "_id" : ObjectId("633adbf1c2973e9852d04dcf"), "name" : "蜘蛛精", "age" : 24, "address" : "蜘蛛洞" }
练习
练习1:尝试阅读下面的语句
其实我们可以把下面的语句当作一个
json
对象来看待,db.collectionName.operate
是固定的写法,之后可以用json
的方式看待向集合
ljq
中插入一个数组,数组中有两个文档,文档内有一个hobby
的文档,这个hobby
文档被称作内嵌文档,然后hobby
文档内有两个数组。注意:这里的代码我为了方便注释,写了# 在粘贴复制的时候请去掉这个
db.ljq.insert([
{
name:"大大白",
age: 28,
hobby:{ # 是一个对象,对量里面分别是数组
music:['new Boy', '云烟成雨', '秋酿'],
games:['王者荣耀', '和平精英', '光遇']
}
},
{
name: "小白",
age: 21,
hobby:{
movies:['大话西游', '唐伯虎点秋香'],
games:['王者荣耀', '旅行青蛙', '穿越火线']
}
}
]);
# 演示
> db.ljq.insert([
... {
... name:"大大白",
... age: 28,
... hobby:{
... music:['new Boy', '云烟成雨', '秋酿'],
... games:['王者荣耀', '和平精英', '光遇']
... }
... },
... {
... name: "小白",
... age: 21,
... hobby:{
... movies:['大话西游', '唐伯虎点秋香'],
... games:['王者荣耀', '旅行青蛙', '穿越火线']
... }
... }
... ]);
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 2,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
> db.ljq.find({"hobby.games":"王者荣耀"});
{ "_id" : ObjectId("633ae22ec2973e9852d04dd0"), "name" : "大大白", "age" : 28, "hobby" : { "music" : [ "new Boy", "云烟成雨", "秋酿" ], "games" : [ "王者荣耀", "和平精英", "光遇" ] } }
{ "_id" : ObjectId("633ae22ec2973e9852d04dd1"), "name" : "小白", "age" : 21, "hobby" : { "movies" : [ "大话西游", "唐伯虎点秋香" ], "games" : [ "王者荣耀", "旅行青蛙", "穿越火线" ] } }
练习2:插入20000条数据,有哪种是比较快捷的方式?
吐个槽,这玩意真的不是用
js
写的嘛
# 这是方式一,这种方式是需要调用insert语句20000次,效率会十分低下,那么可不可以,
# 只调用一次insert语句呢?如果可以的话,应该怎么实现呢?
# 可以将数据先存储起来,然后一次性加入。
for(let i = 1; i <= 20000; i++){
db.nums.insert({num:i});
}
# 这是将数据全部存到了数组中,随后再将数组插入,只执行了一次insert语句。
# 如果还有更好的想法都可以去尝试。还有就是自己给自己出题目,如果都能够实现的话,
# 那么说明你学的差不多了。
const arr = [];
for(let i = 1; i <= 20000; i++){
arr.push({num:i});
}
db.nums.insert(arr);
文档间的关系(三种)
一对一
内联文档
# 可以看到一个妻子是对应一个丈夫的,这是使用内嵌文档来实现的。
db.ljq.insert([
{
name:"zcc",
age: 26,
handsband:{
name:"ljq",
age: 25
}
},
{
name: "ljq",
age: 25,
handsband:{
name: "zcc",
age: 26
}
}
]);
一对多
父母 - 孩子
文章 - 评论
内嵌文档也可以实现,就是一个文档内嵌多个文档,不过比较繁琐
用户一个文档,订单一个文档,在使用的时候再一一对应
# 第一句是创建了两个用户, 分别是花木兰和诸葛亮
db.users.insert([{name:"花木兰"}, {name:"诸葛亮"}]);
# 这一句是查询用户内容, 这是需要知道用户的_id,从而在第三句加入
db.users.find();
# 这是生成了一个订单,并且user_id存储的是花木兰的id
db.orders.insert({list_name:["配马", "鞍鞯"], user_id: ObjectId("633ae419c2973e9852d09bf4")})
# 再生成了一个订单,并且user_id存储的是花木兰的id
db.orders.insert({list_name:["蜜糖", "男装"], user_id: ObjectId("633ae419c2973e9852d09bf5")})
# 在users里查找到 花木兰 这个用户的id,并将其存储起来
let userId = db.users.findOne({name:"花木兰"})._id;
## 使用id来查找
db.orders.find({user_id: userId});
> db.users.insert([{name:"花木兰"}, {name:"诸葛亮"}]);
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 2,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
> db.users.find();
{ "_id" : ObjectId("633ae419c2973e9852d09bf4"), "name" : "花木兰" }
{ "_id" : ObjectId("633ae419c2973e9852d09bf5"), "name" : "诸葛亮" }
> db.orders.insert({list_name:["配马", "鞍鞯"], user_id: ObjectId("633ae419c2973e9852d09bf4")})
WriteResult({ "nInserted" : 1 })
> db.orders.insert({list_name:["蜜糖", "男装"], user_id: ObjectId("633ae419c2973e9852d09bf5")})
WriteResult({ "nInserted" : 1 })
> let userId = db.users.findOne({name:"花木兰"})._id;
> db.orders.find({user_id: userId});
{ "_id" : ObjectId("633ae44f88b15d56aa4bd92c"), "list_name" : [ "配马", "鞍鞯" ], "user_id" : ObjectId("633ae419c2973e9852d09bf4") }
多对多
学生 - 老师
商品 - 分类
可以使用内嵌文档的形式完成
db.teas.insert([{name:"诸葛亮"}, {name:"龟仙人"}, {name:"唐僧"}]);
db.teas.find();
db.stus.insert([
{
name: "孙悟空",
// 插入的是 龟仙人和唐僧的id
teas_id:[ObjectId("633ae5b1aca876176e473c1c"), ObjectId("633ae5b1aca876176e473c1d")]
},
{
name: "刘禅",
// 插入的是 诸葛亮和龟仙人的id
teas_id:[ObjectId("633ae5b1aca876176e473c1b"),ObjectId("633ae5b1aca876176e473c1c")]
}
]);
db.stus.find();
投影
就是限制条件,显示效果
创建一个集合
# 这样创建的是一个列表集合。
db.staff.insert([
{name:"小白", age: 18, salary: 3000},
{name:"大白", age: 19, salary: 3500},
{name:"熊大", age: 23, salary: 3200},
{name:"熊二", age: 22, salary: 3100},
{name:"光头强", age: 27, salary: 3400},
{name:"小鲤鱼", age: 17, salary: 1500},
{name:"奥特曼", age: 39, salary: 4500}
]);
需求一:按照薪资排列,可以使用sort()
方法
sort(condition)
# 表示按照薪资升序排列
sort({salary: 1})
# 表示按照薪资降序排列
sort({salary: -1})
# 这样使用
> db.staff.find().sort({salary: 1});
{ "_id" : ObjectId("633afb3492b25832d18a9f0f"), "name" : "小鲤鱼", "age" : 17, "salary" : 1500 }
{ "_id" : ObjectId("633afb3492b25832d18a9f0a"), "name" : "小白", "age" : 18, "salary" : 3000 }
{ "_id" : ObjectId("633afb3492b25832d18a9f0d"), "name" : "熊二", "age" : 22, "salary" : 3100 }
{ "_id" : ObjectId("633afb3492b25832d18a9f0c"), "name" : "熊大", "age" : 23, "salary" : 3200 }
{ "_id" : ObjectId("633afb3492b25832d18a9f0e"), "name" : "光头强", "age" : 27, "salary" : 3400 }
{ "_id" : ObjectId("633afb3492b25832d18a9f0b"), "name" : "大白", "age" : 19, "salary" : 3500 }
{ "_id" : ObjectId("633afb3492b25832d18a9f10"), "name" : "奥特曼", "age" : 39, "salary" : 4500 }
# 多条件排序 这句的含义是按照薪资升序排序,如果出现了相同的薪资的人,则按照年龄降序排序。
> db.staff.find().sort({salary: 1, age: -1});
{ "_id" : ObjectId("633afb3492b25832d18a9f0f"), "name" : "小鲤鱼", "age" : 17, "salary" : 1500 }
{ "_id" : ObjectId("633afb3492b25832d18a9f0a"), "name" : "小白", "age" : 18, "salary" : 3000 }
{ "_id" : ObjectId("633afb3492b25832d18a9f0d"), "name" : "熊二", "age" : 22, "salary" : 3100 }
{ "_id" : ObjectId("633afb3492b25832d18a9f0c"), "name" : "熊大", "age" : 23, "salary" : 3200 }
{ "_id" : ObjectId("633afb3492b25832d18a9f0e"), "name" : "光头强", "age" : 27, "salary" : 3400 }
{ "_id" : ObjectId("633afb3492b25832d18a9f0b"), "name" : "大白", "age" : 19, "salary" : 3500 }
{ "_id" : ObjectId("633afb3492b25832d18a9f10"), "name" : "奥特曼", "age" : 39, "salary" : 4500 }
分页
db.staff.find().limit(2);
# 表示只会显示前两个数据
> db.staff.find().limit(2);
{ "_id" : ObjectId("633afb3492b25832d18a9f0a"), "name" : "小白", "age" : 18, "salary" : 3000 }
{ "_id" : ObjectId("633afb3492b25832d18a9f0b"), "name" : "大白", "age" : 19, "salary" : 3500 }
在查询时可以在find()
中的第二个参数的位置传入 投影,{name: 1, _id: 0}
: 表示显示name
属性,不显示_id
属性。
# 这句的含义是只显示 name和salary属性,不显示 _id属性
db.staff.find({}, {name: 1, _id: 0, salary: 1});
> db.staff.find({}, {name: 1, _id: 0, salary: 1});
{ "name" : "小白", "salary" : 3000 }
{ "name" : "大白", "salary" : 3500 }
{ "name" : "熊大", "salary" : 3200 }
{ "name" : "熊二", "salary" : 3100 }
{ "name" : "光头强", "salary" : 3400 }
{ "name" : "小鲤鱼", "salary" : 1500 }
{ "name" : "奥特曼", "salary" : 4500 }
mongoose
概念
什么是mongoose
?
一般我们不会直接用
MongoDB
的函数来操作MongoDB
数据库 , 而Mongose
就是一套操作MongoDB
数据库的接口。简而言之就是:
mongoose
也可以操作mongodb
数据库,而且它来操作数据库会有更多好处
为什么要使用mongoose
? mongoose
的优点有哪些?
- 可以为
mongodb
的文档创建一个模式结构(Schema
),说白了就是一个约束的条件 - 比原生的
Node
相比更容易操作mongodb
数据库,也就是更方便 - 可以使用中间件和其他的应用业务挂钩,可以和其他的应用结合使用
- 数据可以通过类型转换转换成对象模型,也就是有方法可以使用。
Mongoose
的三个新对象
Schema
(模式对象)
定义了约束
mongodb
的文档结构的条件
Model
相当于
mongodb
中的collection
(集合)
Document
document
表示集合中的具体文档
值得注意的是这三个都是对象,是对象的话则有它们对应的方法和属性。
vscode
使用mongoose
来操作数据库
// 1、引入 mongoose 模块
var mongoose = require('mongoose');
// 2、连接数据库 mongodb://账号:密码@192.168.76.128
var url = "mongodb://admin:admin@192.168.76.128";
mongoose.connect(url)
.then(() => console.log("Connection Successful"))
.catch(err => console.log(err));;
// 2.1、这是对数据库的监听,而且是使用once, 只监听一次就行了
mongoose.connection.once('open', (err) => {
if (!err) {
console.log("数据库已连接...");
}
});
mongoose.connection.once('close', (err) => {
if (!err) {
console.log("数据库已断开!!!");
}
});
// 3、重命名,简化操作
var Schema = mongoose.Schema;
// 3.1、创建了约束条件:type是类型,default是默认是女,如果是女的话则不用写性别这个属性值了。
var stuSch = new Schema({
name: String,
age: Number,
gender: {
type: String,
default: "女"
},
address: String
});
// 4、创建了模型, 数据库中的集合名是 stuSch(会自动加s), 第二个参数是标准,即是约束条件: stuSch
var stuModel = mongoose.model('student', stuSch);
// 5、使用模型创建对象, 当想创建多个的时候,可以使用数组里面存储对象的方式
// stuModel.create({
// name: "西施",
// age: 18,
// address: "王者峡谷"
// }, (err) => {
// console.log(err);
// });
// stuModel.deleteMany({},function(err) {
// console.log(err)
// });
//stuModel.find({},function(err,doc) {
// console.log(doc)
//});
// stuModel.updateOne({name:"西施", age:'123123123'},function(err,doc) {
// console.log(doc)
// });