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 进行交互,这里演示两个图形化工具: