# Mongoose 连接数据库,Schema 规范,实现增删查改
TIP
本节正式开学 MongoDB 连接 NodeJS,实现 MongoDB 数据库的增删查改操作
- NodeJS 连接 MongoDB ,NodeJS 原生实现增、删、查、改操作
- Mongoose 连接数据库,Schema 数据类型、验证规范
- 配置 MongoDB 数据库的环境变量
- Mongoose 数据库的增、删、查、改
- Mongoose 条件控制
我们前面学习了通过 MongoDB Shell 客户端 mongosh 命令行、MongoDB GUI 图形界面 Compass 客户端连接 MongoDB 服务端,接下来我们开始使用 NodeJS 连接
# 一、NodeJS 连接 MongoDB
TIP
体验如何使用 NodeJS 连接 MongoDB,即:通过 NodeJS 程序来操作数据库,未来不会真正在项目中应用。
Mongoose 是项目中用来连接数据库的工具,但小本节内容是学习 Mongoose 的核心基础
# 1、项目前后端设计
再次回顾下图 “评论系统 - 前后端设计”
注:
- 左边是前端,右边是后端(目前图中是将 NodeJS 和 数据库放在一起了)
- 实际上数据库是独立于 web server 存在的,拆开来看 “创建新评论” 和 “获取评论列表” 是两个接口(路由),它们是通过 NodeJS 来实现的(具体的请求响应过程之前有讲过,只是模拟数据库实现的)
- 接下来需要完成 NodeJS 与 数据库的交互(即:让 NodeJS 操作数据库,先不涉及路由),最后在打通路由的层级
# 2、NodeJS 连接 MongoDB 数据库
新建项目 node-mongodb
# 初始化 package.json
npm init -y
安装 nodemon
npm i nodemon -D
在 package.json
的 script 节点中添加 默认的启动命令
"scripts": {
"dev": "nodemon ./src/index.js"
}
在根目录中新建 src
文件夹,同时新建 src/index.js
node-mongodb
├─ package-lock.json
├─ package.json
└─ src
└─ index.js
# 2.1、npm 安装 MongoDB 包
npm 官方链接地址:https://www.npmjs.com/package/mongodb (opens new window)
npm i mongodb@^5.0.0 --save
注:
mongodb npm 包的版本 要与 NodeJS 的版本对应起来,否则会有版本不兼容的问题,无法启动运行连接数据库的程序
# 2.2、NodeJS 连接 MongoDB 服务
TIP
在 src/index.js
编写 NodeJS 连接 MongoDB 的代码,不涉及路由(项目会使用 Mongoose 对接路由的功能)
// NodeJS 连接 MongoDB
// 对数据库来说:Compass、命令行、NodeJS 都是数据库的客户端
const { MongoClient } = require("mongodb");
// Connection URL
const url = "mongodb://localhost:27017"; // 本地启动的 MongoDB 服务
// Database Name(评论系统项目的数据库)
const dbName = "comment-system";
// 实例化 client
const client = new MongoClient(url);
async function main() {
// 使用 connect 方法,连接到服务器
await client.connect();
console.log("已成功连接到 MongoDB 服务器 !");
// 切换数据库(到指定的 comment-system 数据库)
const db = client.db(dbName);
// 下面的代码示例可以粘贴到这里 ...
return "结束";
}
// 执行 main() 函数,它返回一个 Promise
main()
.then(console.log) // 如果 main() 成功完成,打印其返回值
.catch(console.error) // 如果 main() 发生错误,打印错误信息
.finally(() => {
client.close();
}); // 无论成功还是失败,都执行 client.close() 以清理资源
在命令行终端启动,使用 nodemon 启动
npm run dev
注:
以上打印输出 “结束” 后,控制台并没有退出,是因为 nodemon 一直在监听程序代码的变化
# 2.3、查看是否关闭数据库连接
TIP
想要观察是否退出 或 关闭 MongoDB 数据库连接,可使用 node 命令启动
node .\src\index.js
注:
使用 node 命令启动运行程序后,立马就退出了该程序(即:数据库连接关闭成功了)
# 3、查询数据
TIP
在 src/index.js
中,实现 MongoDB 的数据查询
// NodeJS 连接 MongoDB
// 对数据库来说:Compass、命令行、NodeJS 都是数据库的客户端
const { MongoClient } = require("mongodb");
// Connection URL
const url = "mongodb://localhost:27017"; // 本地启动的 MongoDB 服务
// Database Name(评论系统项目的数据库)
const dbName = "comment-system";
// 实例化 client
const client = new MongoClient(url);
async function main() {
// 使用 connect 方法,连接到服务器
await client.connect();
console.log("已成功连接到 MongoDB 服务器 !");
// 切换数据库 database(指定的 comment-system 数据库)
const db = client.db(dbName);
// 切换到指定的结合 collection(users 集合)
const userCollection = db.collection("users");
// 查询所有用户数据
// const userResult = await userCollection.find().toArray()
// 按条件查询
// const userResult = await userCollection.find({ city: 'beijing', username: 'icoding' }).toArray()
// 排序(根据年龄逆序排列),大部分时候我们根据更新时间排序,目前数据库还没有时间,也可根据 id 来查询,将条件换成 { _id: -1 }
const userResult = await userCollection.find().sort({ age: -1 }).toArray();
console.log("查询结果:", userResult);
return "结束";
}
// 执行 main() 函数,它返回一个 Promise
main()
.then(console.log) // 如果 main() 成功完成,打印其返回值
.catch(console.error) // 如果 main() 发生错误,打印错误信息
.finally(() => {
client.close();
}); // 无论成功还是失败,都执行 client.close() 以清理资源
在命令行终端启动
npm run dev
# 4、新增数据
TIP
新增一条用户文档数据,使用 insertOne
方法
在 src/index.js
中,实现 MongoDB 新增一条用户文档数据
// 新增一条用户文档数据
const insertUserResult = await userCollection.insertOne({
username: "美美",
password: "123",
age: 25,
city: "shenzhen",
});
console.log("新增结果:", insertUserResult);
注:新增信息时,对象中的字段多少没有限制。这也是 MongoDB 的优势非常灵活
注:
当你看到返回的是new ObjectId("65e92b5d6e5bc6df6633e145")
这样的结果时,这通常意味着你正在查看一个对象表示,而不是一个纯字符串。在 JavaScript 中,new ObjectId(...)
是创建一个新的ObjectId
对象的语法。
如果你想要获取_id
的字符串表示形式,你可以调用ObjectId
对象的str
方法,或者使用toString()
方法。
// 新增一条用户文档数据
const insertUserResult = await userCollection.insertOne({
username: "静静",
password: "123",
age: 23,
city: "xian",
});
console.log(
"新增结果:",
insertUserResult.acknowledged,
insertUserResult.insertedId.toString()
);
# 5、修改数据
TIP
修改一条用户文档数据,使用 updateOne
方法
在 src/index.js
中,实现 MongoDB 修改一条用户文档数据
// 修改数据
// 更新第一个用户名为 icoding 的用户信息,将 年龄 改为 36、城市改为 hangzhou
const updateUserResult = await userCollection.updateOne(
{ username: "icoding" }, // 修改的条件
{ $set: { age: 36, city: "hangzhou" } } // 修改的内容
);
console.log("修改结果:", updateUserResult);
// modifiedCount 表示 已修改了几行(命中了几行)
console.log("命中的行数:", updateUserResult.modifiedCount);
# 6、删除数据
TIP
删除一条用户文档数据,使用 deleteOne
方法
在 src/index.js
中,实现 MongoDB 删除一条用户文档数据
// 删除数据
// 删除用户名为 icoding 的用户信息
const deleteUserResult = await userCollection.deleteOne({
username: "icoding",
});
console.log("删除结果:", deleteUserResult);
# 7、总结
TIP
以上的学习中,我们完成了
- 安装 MongoDB npm 插件,即:Mongoose
- 连接数据库,连接集合
- 操作文档,增删查改
# 二、Mongoose 连接数据库,数据类型
TIP
深入浅出 Mongoose 连接 和 操作 MongoDB 数据库
- Mongoose 是什么
- Schema 和 Model
- 基于 Model 操作数据
- 完善评论系统项目路由的功能
# 1、Mongoose 是什么
TIP
Mongoose 是一个对象文档模型(ODM)库,用于 Node.js 中操作 MongoDB 数据库。
它将数据库中的数据转换为 JavaScript 对象,封装了 MongoDB 对文档的增删改查等常用方法,使得在 Node.js 中操作 MongoDB 数据库变得更加灵活简单。Mongoose 提供了 Schema、Model 和 Document 三个主要对象,分别用于定义数据库中的文档结构、表示集合中的所有文档以及表示集合中的具体文档。
此外,Mongoose 还提供了验证和类型转换等功能,以及更多的高级特性和用法。总的来说,Mongoose 是一个强大的工具,使得在 Node.js 中操作 MongoDB 数据库变得更加简单和高效。
注:
- 使用 MongoDB Shell 客户端 mongosh 在命令行中与 MongoDB 服务端进行手动交互。即:在命令行中输入命令 -> “回车” 后,将请求发送给 MongoDB 服务端 -> 服务端再将结果返回给 “命令行”。此方式学习阶段没问题,但项目在正式运行中是不行的。
- Compass 图形界面 与 MongoDB 服务端交互,也是同理。
- 使用 代码程序 与 MongoDB 服务端建立连接 并 发送请求,将结果返回给代码程序,最终将结果做处理后返回给用户即可
这样,就可以实现全部的自动化了,而 Mongoose 就是这样一个工具。有了 Mongoose 后,我们就可以在代码中连接 MongoDB 的数据库 并 做一些交互(文档的增删查改)等场景都可以通过代码来实现。
# 2、MongoDB 的数据类型特点
TIP
由于 MongoDB 的数据格式过于灵活
- 可以插入任何数据,不受限制
- 实际项目开发时,要有数据格式的规范(重要)。如:插入数据时一定要规定好具体有哪些字段,不能少也不能多。
- 特别是多人协作开发时
Mongoose 就提供了数据的规范,就可以用来做约束,按照既定数据规范来实现业务功能即可,不需要数据格式灵活性和创新
# 3、Mongoose 的作用
TIP
Mongoose 是一个用于在 Node.js 中操作 MongoDB 数据库的模块,它是一个对象文档模型(ODM)库,对 Node 原生的 MongoDB 模块进行了优化封装,并提供了更多的功能。Mongoose 的主要作用体现在以下几个方面:
连接 MongoDB 数据库:Mongoose 提供了一个简单的方法来连接 MongoDB 数据库,使得在 Node.js 中访问数据库变得轻松。
模型定义与数据建模:Mongoose 允许你使用模式(Schema)来定义数据库中文档的结构,类似于传统数据库中的表。通过定义模型,你可以描述数据结构和数据之间的关系,确保数据库中存储的文档满足特定的格式规范。这种模型在 MongoDB 中更为灵活,可以动态地调整数据结构。
数据验证:Mongoose 提供了内置的验证器,可以自动对文档中的数据进行验证,确保数据的一致性和有效性。你可以在模型中定义规则,例如字段的最小长度、最大长度、必填字段等,以保证数据的完整性。
数据处理:Mongoose 提供了一组强大的 API,可以用来对数据库中的数据进行增删改查操作,从而方便地处理数据。
中间件:Mongoose 允许你使用中间件来扩展模型的功能,例如添加自定义的业务逻辑。
Mongoose 在 Node.js 中操作 MongoDB 数据库时,提供了连接、建模、验证、处理数据和扩展功能等一系列便利的功能,使得开发者能够更加高效、灵活地与 MongoDB 数据库进行交互
# 4、Mongoose 数据规范
TIP
- Schema 定义数据格式的规范
- 以 Model 规范 Collection
- 规范数据操作的 API
# 5、使用 Mongoose 连接数据库
新建 icoding-mongoose
项目,完成 Mongoose 连接数据库
# 初始化 package.json
npm init -y
安装 nodemon
npm i nodemon -D
在 package.json
的 script 节点中添加 默认的启动命令
"scripts": {
"dev": "nodemon ./src/index.js"
}
安装 Mongoose
npm i mongoose --save
在根目录中新建 src/db
文件夹,同时新建 src/db//db.js
icoding-mongoose
├─ package-lock.json
├─ package.json
└─ src
└─ db
└─ db.js
在 src/db//db.js
中完成 Mongoose 连接数据库的操作
// 使用 Mongoose 连接数据库 MongoDB 的服务端
// 导入 mongoose
const mongoose = require("mongoose");
// Connection URL
const url = "mongodb://localhost:27017"; // 本地启动的 MongoDB 服务
// Database Name(评论系统项目的数据库)如果该数据库不存在,会自动创建
const dbName = "icoding-comment-system";
// 开始连接数据库
mongoose.connect(`${url}/${dbName}`);
// 获取数据库的连接对象
const conn = mongoose.connection;
// 设置连接成功的回调
conn.on("open", () => {
console.log("数据库连接成功 !");
});
// 连接异常(将端口改错来测试),设置连接错误的回调
conn.on("error", (err) => {
console.log("数据库连接失败 !" + err);
});
// 设置连接关闭的回调(下边关闭连接完成后,就会执行该回调函数)
conn.on("close", () => {
console.log("数据库连接关闭 !");
});
// 关闭 MongoDB 的连接,设置一个延时操作,等待 2s 后关闭
setTimeout(() => {
mongoose.disconnect();
}, 2000);
在 VSCode 命令行终端中运行
node .\src\db\db.js
# 6、新增文档
TIP
- 创建文档的结构对象 Schema
- 创建模型对象,对文档操作的封装对象
- 完成文档的新增
在 /src/base/create.js
中
// 使用 Mongoose 连接数据库 MongoDB 的服务端
const mongoose = require("mongoose");
// Connection URL
const url = "mongodb://localhost:27017"; // 本地启动的 MongoDB 服务
// Database Name(评论系统项目的数据库)如果该数据库不存在,会自动创建
const dbName = "icoding-comment-system";
// 开始连接数据库
mongoose.connect(`${url}/${dbName}`);
// 获取数据库的连接对象
const conn = mongoose.connection;
// 设置连接成功的回调
conn.on("open", () => {
console.log("数据库连接成功 !");
// 新增文档数据 ------------------
// 1、创建文档的结构对象(设置集合中文档的属性 和 属性值的类型)
const UserSchema = new mongoose.Schema(
{
username: {
type: String,
required: true, // 必填
unique: true, // 唯一,不重复
},
password: String,
age: Number,
city: String,
// 性别
gender: {
type: Number,
// 默认值
default: 0, // 0 - 保密,1 - 男,2 - 女
},
},
{
timestamps: true, // 时间戳,自动添加文档的创建时间和更新时间
}
);
// 2、创建模型对象,对文档操作的封装对象(用户文档数据需要依赖于 UserSchema 数据格式的定义)
const UserModel = mongoose.model("user", UserSchema);
// 3、新增文档数据
UserModel.create({
username: "icoding",
password: "123",
age: 18,
city: "beijing",
gender: 1,
})
.then((res) => {
console.log("新增文档数据成功:", res);
})
.catch((err) => {
console.error("新增文档数据失败:", err);
});
});
// 连接异常(将端口改错来测试),设置连接错误的回调
conn.on("error", (err) => {
console.log("数据库连接失败 !" + err);
});
// 设置连接关闭的回调(下边关闭连接完成后,就会执行该回调函数)
conn.on("close", () => {
console.log("数据库连接关闭 !");
});
// 关闭 MongoDB 的连接,设置一个延时操作,等待 2s 后关闭
// setTimeout(() => {
// mongoose.disconnect()
// }, 2000)
在 VSCode 命令行终端中运行
node .\src\base\create.js
在 Compass 中查看新增成功后的文档数据(数据库 和 文档会被自动创建)
# 7、Schema 中的内置字段类型
TIP
这些数据类型可以在定义 Mongoose 的 Schema 时指定,以确保存储在 MongoDB 中的数据具有正确的格式和结构。
即:文档属性值的类型
数据类型 | 描述 |
---|---|
String | 用于存储文本字符串。 |
Number | 用于存储数字,包括整数和浮点数。 |
Date | 用于存储日期和时间。 |
Buffer | Buffer 对象,用于存储二进制数据。 |
Boolean | 用于存储布尔值,即true 或false 。 |
Mixed | 用于存储任意类型的数据,相当于 JavaScript 中的any 类型。 |
ObjectId | 用于存储 MongoDB 文档的 ID,通常是自动生成的。 |
Array | 用于存储数组,数组中的元素可以是任意类型。 也可用 [] 来标识 |
Decimal128 | 高精度数字 |
{
// 商品标题,String 字符串类型
product_title: String,
// 商品价格,Number 数字类型
price: Number,
// 商品上市时间,Date 时间日期类型
time_to_market: Date,
// 用于存储文件的二进制数据(将图片 或 视频 文件等转换成二进制格式存储)
// 实际开发中不会这么做,一般都只存储 文件的路径在数据库中,将文件存在静态资源服务器 或 文件夹中,这样效率更高(Buffer 了解即可,开发中几乎不用)
file: Buffer,
// 是否热门,Boolean 布尔值
is_hot: Boolean,
// 数组
tags: Array,
// 测试任意类型的数据
test_mixed: mongoose.Schema.Types.Mixed,
// 文档类型的 id
// 一般用于做 “外键”,即:将另一个文档的 id 存储在该文档中去(让他们之间产生关联)
// 用于联合查询使用,通过该 id 查询另一个文档中关联的数据
test_id: mongoose.Schema.Types.ObjectId,
// 高精度数字,对数字精度要求比较高时会用到
test_decimal128: mongoose.Schema.Types.Decimal128
}
// 赋值
{
product_title: '商品标题',
price: 9.9,
time_to_market: Date.now, // 使用当前时间作为默认值
is_hot: true,
tags: ['HTML', 'CSS', 'JavaScript', 'Python', 'Go'],
test_mixed: 1,
test_id: '65f023d197cdbbb9b24e8fff'
}
# 8、Schema 定义字段值验证规范
TIP
对 Schema 中定义的字段进行验证,防止某些操作对数据库的影响。如缺少未设置字段也可以保存成功等操作
注:作为后端工程师永远不要相信用户的输入
常见验证操作 | 含义 |
---|---|
required | 必须填写 |
default | 默认值 |
validate | 自定义匹配 |
min | 最小值(只适用于数字) |
max | 最大值(只适用于数字) |
match | 正则匹配(只适用于字符串) |
enum | 枚举类型(只适用于字符串) |
minlength | 字符串最小长度 |
maxlength | 字符串最大长度 |
# 8、字段值验证
TIP
Mongoose 有一些内建验证器,可以对文档属性的字段值进行验证。
如:若检测通过,即:放行。就往数据库中插入数据。若检测不合法,就报错禁止插入数据库。
# 8.1、default 默认值
TIP
- 说明:当字段在创建文档时没有被明确赋值时,它将采用
default
指定的默认值。 - 用法:可以是一个固定的值,也可以是一个返回默认值的函数。
username: {
type: String,
default: 'ibc' // 当 username 字段没有赋值时,它将默认为 'ibc'
},
createdAt: {
type: Date,
default: Date.now // 使用当前时间作为默认值
}
# 8.2、required 必填值
TIP
- 说明:标记字段为必需,即在创建或更新文档时,该字段必须存在且不能为
null
或undefined
。 - 用法:通常是一个布尔值,
true
表示字段是必需的,false
表示不是必需的。
username: {
type: String,
required: true, // 必填
unique: true // 唯一,不重复
},
email: {
type: String,
required: true, // email字段在创建或更新文档时是必需的
match: [/^\S+@\S+\.\S+$/, '请提供有效的电子邮件地址']
}
# 8.3、enum 枚举值
TIP
- 说明:限制字段的值只能为枚举数组中的某个元素。如果尝试设置不在枚举数组中的值,将会抛出验证错误。
- 用法:
enum
的值应该是一个数组,包含所有允许的字段值。
// 定义一个枚举字段,只允许指定的值
color: {
type: String, // 使用String类型作为基础类型
// color 字段的值只能是这三个字符串中的一个
enum: ['red', 'green', 'blue'], // 定义枚举值的集合
default: 'green' // 默认值为'green'
}
# 8.4、validate 自定义匹配
TIP
- 说明:自定义验证函数,用于执行更复杂的验证逻辑。
- 用法:提供一个函数,该函数接受字段值作为参数,并返回一个布尔值或错误对象。
age: {
type: Number,
validate: {
validator: function(value) {
return value >= 0 && value <= 150;
},
message: props => `${props.value} 岁不是有效年龄`
}
}
# 8.5、min 最小值
TIP
- 说明:指定字段值的最小值。
- 用法:与数值或字符串长度相关的字段一起使用。
// 数值示例
score: {
type: Number,
min: [0, 'Score 必须大于或等于零']
}
// 字符串长度示例
username: {
type: 'string',
minLength: 3
}
# 8.6、max 最大值
TIP
- 说明:指定字段值的最大值。
- 用法:与
min
类似,但用于指定上限。
age: {
type: Number,
max: [150, '年龄必须小于或等于150']
}
# 8.7、match 正则匹配
TIP
- 说明:使用正则表达式匹配字段值。
- 用法:提供一个正则表达式对象或字符串。
email: {
type: String,
match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, '请填写有效的电子邮件地址']
}
# 8.8、minlength 字符串最小长度
TIP
- 说明:指定字符串字段的最小长度。
- 用法:与字符串字段一起使用。
username: {
type: 'string',
minLength: 4
}
# 8.9、maxlength 字符串最大长度
TIP
- 说明:指定字符串字段的最大长度。
- 用法:与
minlength
类似,但用于指定上限。
description: {
type: 'string',
maxLength: 255
}
# 三、配置 MongoDB 数据库的环境变量
TIP
在 NodeJS 应用程序中连接 MongoDB 数据库时,通常推荐使用环境变量(env variables)来存储敏感信息,如数据库的连接字符串。
这样可以避免在代码库中硬编码这些敏感信息,并且便于在不同环境(如开发、测试、生产)之间切换配置。
# 1、连接 MongoDB 数据库
TIP
首先,我们需要使用 Mongoose 连接到 MongoDB 数据库。
在 src/db/db.js
中
// 使用 Mongoose 连接数据库 MongoDB 的服务端
const mongoose = require("mongoose");
// Connection URL
const url = "mongodb://localhost:27017"; // 本地启动的 MongoDB 服务
// Database Name(评论系统项目的数据库)如果该数据库不存在,会自动创建
const dbName = "icoding-comment-system";
// 开始连接数据库
mongoose
.connect(`${url}/${dbName}`)
.then(() => {
console.log("数据库连接成功 !");
})
.catch((err) => {
console.error("连接数据库失败 !", err);
});
// 导出 mongoose
module.exports = mongoose;
测试数据库连接
node .\src\db\db.js
# 2、创建 .env 文件
TIP
在你的项目根目录下创建一个名为 .env
的文件(注意:在某些系统上,.env
文件可能默认是隐藏的)。在这个文件中,你可以定义环境变量。
MONGODB_HOST = 127.0.0.1
MONGODB_PORT = 27017
MONGODB_USER = ''
MONGODB_PWD = ''
MONGODB_DB = icoding-comment-system
注:
MONGODB_HOST
MongoDB 数据库服务器主机地址(IP 或 域名)MONGODB_PORT
MongoDB 数据库服务器监听的端口号MONGODB_USER
连接 MongoDB 数据库的用户名MONGODB_PWD
连接 MongoDB 数据库的密码MONGODB_DB
要连接的 MongoDB 数据库的名称
出于安全考虑,不应该在代码库或版本控制系统中硬编码这些敏感信息(如用户名和密码)。使用环境变量是一个更好的做法,因为它允许你在不同的环境中(如开发、测试和生产)使用不同的值,而无需修改代码。
同时,确保这些环境变量在生产环境中是安全地存储和管理的。
# 3、安装 dotenv
TIP
安装 dotenv
包来读取 .env
文件中的环境变量
npm install dotenv --save
# 4、在代码中读取环境变量
TIP
在 NodeJS 代码中,使用 dotenv
包来加载 .env
文件中的环境变量,然后可以使用 process.env
对象来访问它们
创建 src/config/config.default.js
配置文件,用于读取环境变量的信息
// 导入 dotenv 包 ,将环境变量从文件加载到进程中
const dotenv = require("dotenv");
// 读取配置信息(加载 .env 文件中的环境变量)
dotenv.config();
// process 代表进程 ,env 代表环境变量
// console.log(process.env.MONGODB_PORT)
// 导出环境变量
module.exports = process.env;
# 5、改造数据库连接
TIP
将原来代码中的数据库相关信息,改成从环境变量中读取的信息
在 src/db/db.js
中
// 导入 env 环境变量
const {
MONGODB_HOST,
MONGODB_PORT,
MONGODB_USER,
MONGODB_PWD,
MONGODB_DB,
} = require("../config/config.default");
// 使用 Mongoose 连接数据库 MongoDB 的服务端
const mongoose = require("mongoose");
// // Connection URL
// const url = 'mongodb://localhost:27017'; // 本地启动的 MongoDB 服务
// // Database Name(评论系统项目的数据库)如果该数据库不存在,会自动创建
// const dbName = 'icoding-comment-system';
// 适应环境变量参数
// 开始连接数据库
mongoose
.connect(`mongodb://${MONGODB_HOST}:${MONGODB_PORT}/${MONGODB_DB}`)
.then(() => {
console.log("数据库连接成功 !");
})
.catch((err) => {
console.error("连接数据库失败 !", err);
});
// 导出 mongoose(commonJS 语法)
module.exports = mongoose;
在命令行终端,测试启动连接数据库
node .\src\db\db.js
# 6、确保 .env 文件不被提交到版本控制
TIP
通常,不希望将 .env
文件提交到版本控制(如 Git),因为这可能会泄露敏感信息。可以在 .gitignore
文件中添加 .env
来确保它不会被提交。
# .gitignore
.env
# 7、处理不同的环境
TIP
对于不同的环境(如开发、测试和生产),你可能需要不同的配置。一种常见的做法是为每个环境创建一个单独的 .env
文件(如 .env.development
、.env.test
、.env.production
),并在运行应用程序时指定要加载哪个文件。
可以使用
dotenv
的dotenv.config({ path: 'path/to/.env' })
方法来加载特定路径的.env
文件。
# 8、在生产环境中保护环境变量
TIP
在生产环境中,最佳实践是将环境变量设置为操作系统级别的环境变量,而不是将它们存储在文件中。
这样,即使攻击者能够访问你的服务器文件,他们也无法直接读取这些环境变量。具体的设置方法取决于你使用的操作系统和部署方式。
# 四、Mongoose 数据库的增、删、查、改
TIP
使用 Mongoose 工具实现数据库的增(创建)、删(删除)、查(查询)、改(更新)
# 1、定义模型(Schema)
TIP
需要定义一个 Mongoose 模型,它基于 MongoDB 的集合和文档结构
创建 src/model/user.model.js
// 数据模型(规范数据格式)
const { Schema } = require("mongoose");
const mongoose = require("../db/db");
// 定义 Schema
const userSchema = new Schema(
{
username: {
type: String,
required: true, // 必填
unique: true, // 唯一,不重复
},
password: String,
age: Number,
city: String,
// 性别
gender: {
type: Number,
// 默认值
default: 0, // 0 - 保密,1 - 男,2 - 女
},
},
{
timestamps: true, // 时间戳,自动添加文档的创建时间和更新时间
}
);
// 创建模型
const User = mongoose.model("User", userSchema);
// 导出 User 对象
module.exports = User;
# 2、新增文档数据
TIP
使用 Mongoose 模型创建一个新文档(即插入到数据库)
在 src/test/create.js
中
// 导入 User 模型
const User = require("../model/user.model");
// 定义一个 async 的匿名函数,并执行。为了里面能用 await
!(async () => {
// // 创建一个新用户 - 方式 1
// const newUser = new User({
// username: 'allen',
// password: '123',
// age: 21,
// city: 'shanghai',
// gender: 2
// })
// // 保存新用户到数据库
// newUser.save()
// .then(user => {
// console.log('用户创建成功 !', user)
// })
// .catch(err => {
// console.log('用户创建失败 !', err)
// })
// 创建一个新用户 - 方式 2
const newUser = await User.create({
username: "ibc",
password: "123",
age: 25,
city: "shenzhen",
gender: 1,
});
console.log("创建用户成功 !", newUser);
})();
在命令行终端,测试启动新增文档数据
node .\src\test\create.js
# 3、查询文档数据
TIP
查询数据库中的文档,查询所有数据、 查找特定用户(按条件查询)、查询单条数据、分页查询
在 src/test/find-findOne.js
// 导入 User 模型
const User = require("../model/user.model");
!(async () => {
// // 查找所有用户(返回数组)
// const userList = await User.find()
// console.log('所有用户查询结果:', userList)
// // 查找特定用户(按条件查询)
// const userList = await User.find({ username: 'allen' })
// console.log('按条件查询用户结果:', userList)
// 查询单条数据
// const user = await User.findOne({ city: 'beijing' })
// console.log('查询单条数据:', user)
// 分页查询
// page 请求的页码,默认值 1
// pageSize 每页显示的文档数量, 默认值 10
const getPaginatedData = async (page = 1, pageSize = 10) => {
// 计算需要跳过的文档数量
const skip = (page - 1) * pageSize;
// 执行查询并获取数据
return await User.find().skip(skip).limit(pageSize).exec();
};
// 使用分页查询函数获取数据
// 获取第2页的数据,每页2条记录
getPaginatedData(2, 2)
.then((data) => {
console.log(data); // 输出查询结果
})
.catch((error) => {
console.error("获取分页数据时出错:", error);
});
})();
在命令行终端,测试启动查询文档数据
node .\src\test\find-findOne.js
注:
以上代码中,分页查询的解读
getPaginatedData
函数接受两个参数:page
(页码)和 pageSize
(每页的记录数)。函数内部首先计算需要跳过的文档数量(skip
),然后使用 User.find().skip(skip).limit(pageSize).exec()
执行分页查询。
最后,函数返回查询结果。你可以根据需要调整页码和每页的记录数。
# 4、分页查询详细解读
TIP
以上分页查询的代码中,是一个异步函数 getPaginatedData
,它用于获取分页的数据。该函数接收两个参数:page
和 pageSize
,分别代表请求的页码和每页显示的文档数量。
如果调用函数时没有提供这两个参数,它们将分别默认为 1 和 10。
# 4.1、参数定义
const getPaginatedData = async (page = 1, pageSize = 10) => {};
注:
这里定义了一个异步函数 getPaginatedData
,它有两个参数:page
和 pageSize
。
这两个参数都有默认值,page
的默认值是 1,pageSize
的默认值是 10。
# 4.2、计算需要跳过的文档数量
const skip = (page - 1) * pageSize;
注:
为了获取特定页码的数据,我们需要知道从哪个位置开始查询。这里,我们使用 (page - 1) * pageSize
来计算需要跳过的文档数量。
例如,如果 page
是 2,pageSize
是 10,那么 skip
的值将是 10,意味着我们将跳过前 10 个文档,从而从第 11 个文档开始查询。
# 4.3、执行查询并获取数据
return await User.find().skip(skip).limit(pageSize).exec();
注:
User.find()
:这是一个基于 MongoDB 的查询操作,用于从User
集合中查找文档.skip(skip)
:这是 Mongoose 的一个方法,用于跳过指定数量的文档。在这里,我们跳过skip
个文档.limit(pageSize)
:这也是 Mongoose 的一个方法,用于限制返回的文档数量。在这里,我们限制返回的文档数量为pageSize
.exec()
:这是 Mongoose 的一个方法,用于执行查询并返回结果
# 5、数据排序
// 按 年龄 age 升序排列
const userList = await User.find().sort({ age: 1 }).exec();
注:
sort({ age: 1 })
表示按照age
字段 升序 排列查询结果sort({ age: -1 })
表示按照age
字段 降序 排列查询结果
# 6、更新文档数据
TIP
更新数据库中的现有文档
在 src/test/update.js
中
// 导入 User 模型
const User = require("../model/user.model");
!(async () => {
// 更新用户数据
const updateUserOne = async (username, age) => {
// updateOne命令用于更新集合中匹配到的第一个文档
// 更新第一个用户名为 allen 的用户信息,将 年龄 改为 31
const res = await User.updateOne(
{ username: username }, // 修改的条件
{ $set: { age: age } } // 修改的内容
);
// 返回值为代码改动记录数
return res.modifiedCount > 0 ? true : false;
};
// 更新用户数据
updateUserOne("allen", 36)
.then((data) => {
console.log("用户数据更新成功 !", data);
})
.catch((err) => {
console.log("更新用户信息失败 !", err);
});
})();
在命令行终端,测试启动更新文档数据
node .\src\test\update.js
# 7、删除文档数据
TIP
从数据库中删除文档
在 src/test/delete.js
中
// 导入 User 模型
const User = require("../model/user.model");
!(async () => {
// 删除用户数据
const deleteUserOne = async (username) => {
// deleteOne方法用于删除与指定查询条件匹配的第一个文档
// 删除该用户名的第一个用户信息
const res = await User.deleteOne({ username: username });
// 返回值为代码改动记录数
return res.deletedCount > 0 ? true : false;
};
// 根据用户名删除用户信息
deleteUserOne("ibc")
.then((data) => {
console.log("删除成功!", data);
})
.catch((err) => {
console.log("删除失败!", err);
});
})();
在命令行终端,测试启动删除文档数据
node .\src\test\delete.js
# 五、条件控制
TIP
在 Mongoose 中,可以使用多种条件控制语句、运算符、逻辑运算以及正则匹配来构建查询。
# 1、运算符
TIP
Mongoose 支持 MongoDB 的所有查询运算符。
以下是一些常见的运算符:
# 1.1、比较运算符
比较运算符 | 替代符号 | 描述 |
---|---|---|
= | $eq | 等于 |
> | $gt | 大于 |
>= | $gte | 大于等于 |
< | $lt | 小于 |
<= | $lte | 小于等于 |
!== | $ne | 不等于 |
$in | 在数组中 | |
$nin | 不在数组中 |
$gt
比较运算符,大于的情况
// 查找 age 大于 20 的所有数据(返回数组)
const userList = await User.find({ age: { $gt: 20 } });
$ne
比较运算符,不等于的情况
// 查找 不包含 age = 19 的所有数据(返回数组)
const userList = await User.find({ age: { $ne: 19 } });
# 1.2、元素运算符
元素运算符 | 描述 |
---|---|
$exists | 字段存在 |
$type | 字段类型 |
# 1.3、逻辑运算符
逻辑运算符 | 描述 |
---|---|
$and | 与 |
$or | 或 |
$not | 非 |
$nor | 非或 |
$or
逻辑运算符,或的情况
// 查找 age 等于 20 或 age 等于 22 的所有数据,满足其中一个条件即可(返回数组)
const userList = await User.find({ $or: [{ age: 19 }, { age: 22 }] });
$and
逻辑运算符,与的情况
// 查找 30 > age > 18 的所有数据(返回数组)
const userList = await User.find({
$and: [{ age: { $lt: 30 } }, { age: { $gt: 18 } }],
});
console.log("查询结果:", userList);
$not
逻辑运算符,非的情况
// 查询所在城市 非北京 的用户数据(返回数组)
const userList = await User.find({ city: { $not: { $eq: "beijing" } } });
console.log("查询结果:", userList);
# 1.4、数组运算符
数组运算符 | 描述 |
---|---|
$all | 所有元素匹配 |
$elemMatch | 至少一个元素匹配 |
$size | 数组大小 |
# 1.5、地理空间运算符
地理空间运算符 | 描述 |
---|---|
$near | 近邻搜索 |
$nearSphere | 球面上的近邻搜索 |
$within | 在指定形状内 |
# 1.6、更新运算符
更新运算符 | 描述 |
---|---|
$set | 设置字段值 |
$inc | 增加字段值 |
$mul | 乘以字段值 |
$rename | 重命名字段 |
$unset | 删除字段 |
# 2、正则匹配
结合多个运算符 和 逻辑运算来构建复杂的查询
// 结合多个运算符 和 逻辑运算来构建复杂的查询
const userList = await User.find({
$or: [
{ username: { $regex: new RegExp("new", "i") } },
{ age: { $gte: 18, $lte: 30 } },
],
city: "beijing",
});
注:
这个查询会找到名字中包含 "new"(不区分大小写)或者年龄在 18 到 30 岁之间,并且 city
字段为 beijing
的所有用户。
new RegExp('new', 'i')
创建了一个正则表达式对象,用于模式匹配- 其中,
'i'
是一个标志(flag),它表示“不区分大小写”(ignore case)
因此,当你使用 { username: { $regex: new RegExp('new', 'i') } }
作为查询条件时,它会匹配所有 username
字段中包含 "new"(不区分大小写)的文档。这意味着 "New"、"nEw"、"neW"、“NEW” 等都会被视为匹配项。
简而言之,
'i'
标志允许你在进行正则表达式匹配时忽略大小写。
大厂最新技术学习分享群
微信扫一扫进群,获取资料
X