MongoDB
一、简介
1.1 MongoDB 是什么
MongoDB 是一个基于分布式文件存储的数据库(官方地址)
1.2 数据库是什么
数据库(DataBase)是按照数据结构来组织、存储和管理数据的应用程序
1.3 数据库的作用
数据库的主要作用就是 管理数据,对数据进行 增(c)、删(d)、改(u)、查(r)
1.4 数据库管理数据的特点
相比于纯文件管理数据,数据库管理数据有如下特点:
- 速度更快
- 扩展性更强
- 安全性更强
1.5 为什么选择 MongoDB
操作语法与 JavaScript 类似,容易上手,学习成本低
二、核心概念
MongoDB 中有三个重要概念需要掌握:
- 数据库(DataBase):数据库是一个数据仓库,数据仓服务下可以创建很多数据库,数据库中可以存放很多集合
- 集合(Collection):集合类似于 JS 中的数组,在集合中可以存放很多文档(这个集合就相当于数据表)
- 文档(Document):文档是数据库中的最小单位,类似于 JS 中的对象(这个文档就相当于数据表中的字段)
JSON 文件示例:(用于理解上面的概念)
{ // 一个 json 文件就代表一个数据库
"accounts": [ // 代表一个集合(即数据表)
{ // 每一个对象就是一个文档(即数据表中的字段)
"id": "7JivnCf3M",
"title": "买复习材料",
"time": "2024-03-25",
"type": "-1",
"account": "89",
"remarks": "买书了,就一定要去看啊!"
},
{
"id": "4ZqPLTsJB",
"title": "发工资",
"time": "2024-03-23",
"type": "1",
"account": "90000",
"remarks": "终于发工资了!"
},
{
"id": "l3nKzkhw7",
"title": "报班",
"time": "2024-03-14",
"type": "-1",
"account": "10000",
"remarks": "迫不得已,不得不去"
},
{
"id": "BaA0bRfh8",
"title": "买水果",
"time": "2024-03-01",
"type": "-1",
"account": "20",
"remarks": "好久没吃水果了,今天吃一吃"
}
],
"users": [ // 代表一个集合(即数据表)
{ // 每一个对象就是一个文档(即数据表中的字段)
"id": 1,
"name": "zhangsan",
"age": 18
},
{
"id": 2,
"name": "lisi",
"age": 20
},
{
"id": 3,
"name": "wangwu",
"age": 22
}
]
}
我们可以通过 JSON 文件来理解 MongoDB 中的概念:
- 一个 JSON 文件 好比是一个 数据库,一个 MongoDB 服务下可以有 N 个数据库
- JSON 文件中的 一级属性的数组值 好比是 集合
- 数组中的对象好比是 文档
- 对象中的属性有时也称之为 字段
一般情况下:
- 一个项目使用一个数据库
- 一个集合会存储同一类型的数据
三、下载安装与启动
下载地址:MongoDB 官网
建议选择 zip
类型,通用性更强。因为选择下载的是 .zip 文件,所以直接解压,一步到位
解压后文件里面的目录结构如下:
(1)配置步骤如下:
-
选择任一磁盘创建空文件夹(不要使用中文路径),将解压之后的文件夹内容剪切进去(我选择的是D盘)
-
手动创建 data 和 log 两个文件夹:
-
配置环境变量(打开“开始”搜索“环境变量”即可)
找到 Path,进行编辑,将自己的 MongoDB 的 bin 文件地址添加上去
(2)启动服务:
-
创建 db 文件夹(在 data 目录下,创建 db 文件夹)
因为启动 MongoDB 服务之前必需创建数据量文件的存放文件夹,否则命令不会自动创建,而且不能确定成功。
-
在 bin 目录上直接输入 cmd
-
输入命令
输入如下命令后按回车,后面的路径是 data 文件夹下的 db 目录路径(类似 D:\MongoDB\data\db)
mongod --dbpath D:\MongoDB\data\db
回车后会看到一大堆下面这样的(找到 waiting for connections,则表明服务 已经启动成功)
-
成功
在浏览器中输入下面的地址:
http://localhost:27017/
若显示结果如下,就说明安装成功并结束,也代表服务已经启动成功
-
使用
mongo
连接本地的 MongoDB 服务
注意:
- 为了方便后续使用 mongod 或 mongo 命令,可以将 bin 目录配置到环境变量 Path 中
- 千万不要选中服务端窗口的内容,选中后会停止服务,可以通过 敲回车 取消选中来解决
四、命令行交互
命令行交互一般是学习数据库的第一步,不过这些命令在后续用的比较少,所以我们只需要了解即可
4.1 数据库命令
-
显示所有的数据库
show dbs
-
切换到指定的数据库,如果数据库不存在会自动创建数据库
use 数据库名
-
显示当前所在的数据库
db
-
删除当前数据库
use 库名 db.dropDatabase()
4.2 集合命令
-
创建集合
db.createCollection('集合名称')
-
显示当前数据库中的所有集合
show collections
-
删除某个集合
db.集合名.drop()
-
重命名集合
db.集合名.renameCollection('newName')
4.3 文档命令
-
插入文档
db.集合名.insert(文档对象)
-
查询文档
db.集合名.find(查询条件) // 不加查询条件的话,得到的是所有的文档
_id 是 MongoDB 自动生成的唯一编号,用来唯一标识文档
-
更新文档
// 这个方法相当于重写,即用“新的文档”覆盖掉“旧的文档”来实现更新文档的功能,从而导致文档中不需要更新的内容发生丢失 db.集合名.update(查询条件, 新的文档) // 例如:更新 name 为‘张三’的 age = 35 --> 最终得到的结果虽然更新了age,但是其name属性丢失了 dp.users.update({name: '张三'}, {age: 35}) // 这个方法可以实现更新文档中的某一个数据,并且不会发生数据丢失的情况 db.集合名.update(查询条件, {$set:要更新的文档}) // 例如: db.集合名.update({name: '张三'}, {$set:{age: 19}})
-
删除文档
db.集合名.remove(查询条件)
4.4 应用场景
(1)新增
- 用户注册
- 发布视频
- 发布商品
- 发朋友圈
- 发评论
- 发微博
- 发弹幕
- …
(2)删除
- 删除评论
- 删除商品
- 删除文章
- 删除视频
- 删除微博
- …
(3)更新
- 更新个人信息
- 修改商品价格
- 修改文章内容
- …
(4)查询
- 商品列表
- 视频列表
- 朋友圈列表
- 微博列表
- 搜索功能
- …
五、Mongoose
5.1 介绍
- Mongoose 是一个对象文档模型(ODM)库(Mongoose 官网),它是对 Node.js 原生的 MongoDB 模块进行了进一步的优化封装
- Mongoose 是一个让我们可以通过 Node.js 来操作 MongoDB 数据库的一个模块
- 大多数情况下,它被用来把结构化的模式应用到一个 MongoDB 集合,并提供了验证和类型转换等好处
- 基于 MongoDB 驱动,通过关系型数据库的思想来实现非关系型数据库
5.2 作用
方便使用代码去操作 MongoDB 数据库
优势:
- 为文档创建模式结构(Schema),也可以说是约束
- 对模型中的对象/文档进行验证
- 数据可以通过类型转换为对象模型
- 可以使用中间件来应用业务逻辑挂钩
- 相比 MongoDB 驱动更容易
5.3 使用流程
- 通过 npm 命令去安装 Mongoose:
npm i mongoose
- 项目中引入 mongoose:
const mongoose = require('mongoose')
- 连接 MongoDB 数据库:
mongoose.connect('mongodb://数据库ip地址:端口号(默认端口27017可以省略)/数据库名')
(1)mongoose 初体验
使用 mongoose 完成对 MongoDB 数据库的连接
代码示例:
// 1.安装 mongoose ==> npm i mongoose
// 2.导入 mongoose
const mongoose = require('mongoose')
// 3.连接 mongodb 服务(mongodb 服务的默认端口是27017,所以可写可不写)
// mongodb://127.0.0.1:27017/bilibili 分别是:协议名称 IP地址 端口号 数据库名称
mongoose.connect('mongodb://127.0.0.1:27017/bilibili')
// 4.设置回调
// 设置连接成功的回调
mongoose.connection.on('open', () => {
console.log('连接成功')
})
// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败')
})
// 设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭')
})
// 关闭 mongodb 的连接
setTimeout(() => {
mongoose.disconnect()
}, 2000)
(2)创建新文档
在实现使用 mongoose 对 MongoDB 数据库连接的基础上,完成如下的操作:
创建文档的结构对象(Schema)
let BookSchema = new mongoose.Schema({ // 设置集合中文档的属性以及属性值的类型 例如: name: String, price: Number // ..... })
- 通过
Schema
创建Model
- Model 代表的是数据库中的集合,通过 Model 才能对数据库进行操作
创建模型对象(Model)
let BookModel = mongoose.model('books', BookSchema)
参数:
- 要映射的集合名称
- 创建的约束(Schema 对象)
通过 Model 返回的值对数据进行增、删、改、查(这里实现的是增)
BookModel.create({ name: '西游记', author: '吴承恩', price: 19.9 }).then((data) => { console.log(data) // 插入成功 }).catch((err) => { console.log(err) // 插入失败 })
注意:高版本的 mongoose 的 create 方法是没有第二个参数(即回调函数),而是返回了一个 Promise 对象,所以我们需要采用 then 和 catch 方法来捕获状态。
断开数据库连接(程序运行过程中,一般不会使用)
mongoose.disconnect()
代码示例:
// 1.安装 mongoose
// 2.导入 mongoose
const mongoose = require('mongoose')
// 3.连接 mongodb 服务
mongoose.connect('mongodb://127.0.0.1:27017/bilibili')
// 4.设置回调
// 设置连接成功的回调
mongoose.connection.once('open', () => {
// console.log('连接成功')
// 5.创建文档的结构对象(Schema:模式,可以理解为结构)
// 设置集合中文档的属性以及属性值的类型
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number
})
// 6.创建模型对象(模型对象是对文档操作的封装对象)
// 参数1:集合名称 参数2:结构对象
let BookModel = mongoose.model('books', BookSchema)
// 7.新增
/**
* 报错问题:MongooseError: Model.create() no longer accepts a callback
* 原因:在旧版本中 Model.create() 的第二个参数是一个回调函数,用来捕获成功或异常;但是在最新
* 版本中,Model.create() 中没有第二个参数了,而是返回了一个 Promise 对象,我们将采用
* then 方法和 catch 方法来捕获状态
*
* then:捕获成功
* catch:捕获失败
*/
BookModel.create({
name: '西游记',
author: '吴承恩',
price: 19.9
}).then((data) => {
console.log(data) // 增加成功
// 8.关闭数据库连接(项目运行过程中,不会添加该代码)
mongoose.disconnect()
}).catch((err) => {
console.log(err) // 增加失败
})
})
// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败')
})
// 设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭')
})
5.4 字段类型
文档结构可选的常用字段类型列表:
类型 | 描述 |
---|---|
String | 字符串 |
Number | 数字 |
Boolean | 布尔值 |
Array | 数组,也可以使用 [] 来标识 |
Date | 日期 |
Buffer | Buffer 对象 |
Mixed | 任意类型,需要使用 mongoose.Schema.Types.Mixed 指定 |
ObjectId | 对象 ID,需要使用 mongoose.Schema.Types.ObjectId 指定 |
Decimal128 | 高精度数字,需要使用 mongoose.Schema.Types.Decimal128 指定 |
代码示例:
// 导入 mongoose
const mongoose = require('mongoose')
// 连接 mongodb 服务
mongoose.connect('mongodb://127.0.0.1:27017/mongoose_test')
// 设置回调
// 设置连接成功的回调
mongoose.connection.once('open', () => {
// 创建文档的结构对象
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number,
is_hot: Boolean,
// 关于数组类型的标识:除了使用 Array 外,还可以使用 [] 来标识
tags: [],
pub_time: Date,
test: mongoose.Schema.Types.Mixed
})
// 创建模型对象
let BookModel = mongoose.model('books', BookSchema)
// 新增
BookModel.create({
name: '西游记',
author: '吴承恩',
price: 19.9,
// 属性名被写错时,那么该属性名就会被忽略
is_hot: true, // 如果写的"true"或"false",会自动转换为布尔值
tags: ['鬼怪', '神话', '励志'],
pub_time: new Date(),
test: 99
}).then((data) => {
console.log(data) // 增加成功
// 关闭数据库连接
mongoose.disconnect()
}).catch((err) => {
console.log(err) // 增加失败
})
})
// 设置连接失败的回调
mongoose.connection.on('err', () => {
console.log('连接失败')
})
// 设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭')
})
5.5 字段值验证
Mongoose 有一些内建验证器,可以对字段值进行验证
(1)必填项
// 给 name 属性设置必填项,就必须给 name 赋值,否则会报错(name: Path `name` is required)
name: {
type: String,
required: true // 设置必填项
}
(2)默认值
// 给 author 属性设置默认值
author: {
type: String,
default: '匿名' // 设置默认值(未设置属性值时,会使用该默认值)
}
(3)枚举值
// 给 style 属性设置枚举值
style: {
type: String,
enum: ['神话', '小说', '鬼怪'] // 设置的属性值都必须是该数组中的,否则会报错(style: `a` is not a valid enum value for path `style`)
}
(4)唯一值
// 给 username 设置唯一值,那么该属性的属性值是独一无二的,否则会发生报错
username: {
type: String,
unique: true // 注:unique 需要重新创建集合才能有效果
}
unique 需要 重建集合 才能有效果
开发定理:永远不要相信用户的输入
5.6 CURD
数据库的基本操作包括四个:增加(create)、删除(delete)、修改(update)、查询(read)
(1)增加(create)
-
增加一条数据:(create)
BookModel.create({ name: '西游记', author: '吴承恩', price: 19.9, is_hot: true }).then((data) => { console.log(data) }).catch((err) => { console.log(err) })
-
增加多条数据:(insertMany)
BookModel.insertMany([ { name: '与病毒同行', author: '沐日海洋', price: 9.9, is_hot: true }, { name: '末日技能树', author: '暗黑茄子', price: 9.9, is_hot: true } ]).then((data) => { console.log(data) }).catch((err) => { console.log(err) })
(2)删除(delete)
-
删除一条数据:(deleteOne)
BookModel.deleteOne({_id: '65f7af461fe13910a107685f'}).then((data) => { console.log(data) }).catch((err) => { console.log(err) })
-
删除多条数据:(deleteMany)
BookModel.deleteMany({is_hot: false}).then((data) => { console.log(data) }).catch((err) => { console.log(err) })
(3)修改(update)
-
修改一条数据:(updateOne)
BookModel.updateOne({name: '红楼梦'}, {price: 9.9}).then((data) => { console.log(data) }).catch((err) => { console.log(err) })
-
修改多条数据:(updateMany)
BookModel.updateMany({author: '余华'}, {is_hot: false}).then((data) => { console.log(data) }).catch((err) => { console.log(err) })
(4)查询(read)
-
查询一条数据:
-
findOne:
BookModel.findOne({author: '沐日海洋'}).then((data) => { console.log(data) }).catch((err) => { console.log(err) })
-
findById:
// 还可以使用 findById(根据 id 查询数据) BookModel.findById('65f7cf5bef9a5f1f2084bc90').then((data) => { console.log(data) }).catch((err) => { console.log(err) })
-
-
查询多条数据:(find)
-
不加条件查询(查询所有)
BookModel.find().then((data) => { console.log(data) }).catch((err) => { console.log(err) })
-
加条件查询
BookModel.find({price: 9.9}).then((data) => { console.log(data) }).catch((err) => { console.log(err) })
-
5.7 条件控制
(1)运算符
在 mongodb 中不能使用 > < >= <= !== 等运算符,需要使用替代符号:
-
>
使用$gt
-
<
使用$lt
-
>=
使用$gte
-
<=
使用$lte
-
!==
使用$ne
BookModel.find({price: {$lt: 10}}).then((data) => {
console.log(data)
}).catch((err) => {
console.log(err)
})
(2)逻辑运算
-
$or
逻辑或的情况BookModel.find({$or: [{author: '余华'}, {author: '沐日海洋'}]}).then((data) => { console.log(data) }).catch((err) => { console.log(err) })
-
$and
逻辑与的情况BookModel.find({$and: [{price: {$gt: 30}}, {price: {$lt: 70}}]}).then((data) => { console.log(data) }).catch((err) => { console.log(err) })
(3)正则匹配
条件中可以直接使用 JS 的正则语法,通过正则可以进行模糊查询:
// 语法格式为:(key 表示实现模糊查询的关键字)
// 比如:搜索书籍名称中带有‘三’的图书,那么 key 就为‘三’
BookModel.find({属性名: /key/})
BookModel.find({name: /三/}).then((data) => {
console.log(data)
}).catch((err) => {
console.log(err)
})
另一种写法:
BookModel.find({name: new RegExp('三')}).then((data) => {
console.log(data)
}).catch((err) => {
console.log(err)
})
有些时候关键字究竟是什么,你不确定,它可能放在一个变量当中,而如果是变量的话,将其放在‘/key/’上是无法解析,那么就需要使用 new RegExp(‘key’) 这种方式了
5.8 个性化读取
(1)字段筛选
// 通过 select 可以设置字段,来实现字段筛选 —— 格式:select({属性名: 1}) / select({属性名: 0})
// 0:不要的字段
// 1:要的字段
BookModel.find().select({name: 1, author: 1, _id: 0}).then((data) => {
console.log(data)
}).catch((err) => {
console.log(err)
})
(2)数据排序
// 通过 sort 可以实现数据排序 —— 格式:sort({属性名: 1}) / sort({属性名: -1})
// 1:升序
// -1:降序
BookModel.find().select({name: 1, price: 1, _id: 0}).sort({price: -1}).then((data) => {
console.log(data)
}).catch((err) => {
console.log(err)
})
(3)数据截取
// 通过 skip 可以实现跳过数据 —— 格式:skip(n) --- 跳过 n 条数据
// 通过 limit 可以实现限定获取数据 —— 格式:limit(n) --- 限定获取 n 条数据
// 通常情况下,我们会将 skip 和 limit 一起使用,用来实现分页器
BookModel.find().select({name: 1, price: 1, _id: 0}).sort({price: -1}).skip(3).limit(3).then((data) => {
console.log(data)
}).catch((err) => {
console.log(err)
})
5.9 mongoose 模块化
经过前面的学习,我们会发现有很多代码是一直在重复的书写,这在实际开发中,是很浪费时间的,所以我们需要对 mongoose 模块化,将一些重复的代码提取出来并将其封装起来:
-
创建配置文件夹,再创建配置文件 config.js,然后将IP地址、端口号以及数据库名称写入该文件中:(以后便于修改)
// 配置文件 module.exports = { DBHOST: '127.0.0.1', DBPORT: 27017, DBNAME: 'mongoose_test' }
-
创建 db 文件夹,再创建 db.js 文件,然后将以下代码写入 db.js 文件中:(导入配置文件,获取连接数据库的参数)
/** * * @param {*} success 数据库连接成功的回调 * @param {*} error 数据库连接失败的回调 */ module.exports = function (success, error) { // 判断 error 为其设置默认值 if(typeof error !== 'function') { error = () => { console.log('连接失败~~~') } } // 导入 mongoose const mongoose = require('mongoose') // 导入配置文件(解构赋值) const {DBHOST, DBPORT, DBNAME} = require('../config/config') // 连接 mongodb 服务 mongoose.connect(`mongodb://${DBHOST}:${DBPORT}/${DBNAME}`) // 设置连接成功的回调 mongoose.connection.once('open', () => { success() }) // 设置连接错误的回调 mongoose.connection.on('error', () => { error() }) // 设置连接关闭的回调 mongoose.connection.on('close', () => { console.log('连接关闭') }) }
-
创建 model 文件夹,然后创建不同集合的 Model.js 文件,并写入创建结构对象和创建模型对象的代码:
比如:创建集合 novel 的 BookModel.js 文件
// 导入 mongoose const mongoose = require('mongoose') // 创建文档的结构对象(Schema:模式,可以理解为结构) let BookSchema = new mongoose.Schema({ name: String, author: String, price: Number }) // 创建模型对象(模型对象是对文档操作的封装对象) let BookModel = mongoose.model('novel', BookSchema) // 暴露 BookModel module.exports = BookModel
-
最后,创建 index.js 并导入 db.js 和 Model.js,将对数据库的增删改查都在 index.js 中操作:
// 导入 db 文件 const db = require('./db/db') // 导入 mongoose const mongoose = require('mongoose') // 导入 BookModel const BookModel = require('./model/BookModel') // 调用 db 函数 db(() => { // 查询 BookModel.findOne({ author: '沐日海洋' }).then((data) => { console.log(data) // 关闭数据库连接 mongoose.disconnect() }).catch((err) => { console.log(err) }) })
六、图形化管理工具
我们可以使用图形化的管理工具来对 Mongodb 进行交互,这里演示两个图形化工具: