抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文主要学习了如何在Node中使用MongoDB数据库。

环境

Windows 10 企业版 LTSC 21H2
Node 18.14.0
NPM 9.3.1
NVM 1.1.12
MongoDB 6.0.21
Mongoose 8.13.1

1 介绍

Mongoose是一个对象文档模型库,通过Mongoose可以更方便的在Node中操作MongoDB数据库。

官网地址:http://www.mongoosejs.net/

2 安装

使用npm install mongoose命令安装。

3 连接

有两种方式建立连接:

语法:

js
1
2
3
4
// 建立连接
mongoose.connect('mongodb://username:password@host:port/database?options')
// 关闭连接
mongoose.connection.close()

参数:

  • mongodb:标识标准连接格式。
  • username:用户名,可选。
  • password:密码,可选。
  • host:域名或IP地址。
  • port:端口,可选,默认为27017端口。
  • database:要连接的数据库。
  • options:选项,可选。

示例:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 引入模块
const mongoose = require('mongoose');
// 建立连接
mongoose.connect('mongodb://127.0.0.1:27017/test');
// 执行操作
async function execExample() {
try {
// 执行CRUD操作
} catch (err) {
// 打印错误
console.error("执行失败: " + err);
} finally {
// 关闭连接
mongoose.connection.close();
}
}
execExample();

使用connect()方法返回的Promise对象处理连接错误:

js
1
2
3
4
5
// 引入模块
const mongoose = require('mongoose');
// 建立连接
mongoose.connect('mongodb://127.0.0.1:27017/test')
.catch(err => console.log("连接失败: " + err));

回调事件:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 建立连接失败时执行
mongoose.connection.on('error', err => console.log('error: ' + err));
// 建立连接时和重新连接时执行,可能会执行多次
mongoose.connection.on('connected', () => console.log('connected'));
// 建立连接时和重新连接时执行,可能会执行多次
mongoose.connection.on('open', () => console.log('open'));
// 丢失连接时和关闭连接时执行,可能会执行多次
mongoose.connection.on('disconnected', () => console.log('disconnected'));
// 重新连接时执行
mongoose.connection.on('reconnected', () => console.log('reconnected'));
// 关闭连接时执行
mongoose.connection.on('disconnecting', () => console.log('disconnecting'));
// 关闭连接时执行
mongoose.connection.on('close', () => console.log('close'));

语法:

js
1
2
3
4
// 建立连接
const conn = mongoose.createConnection('mongodb://username:password@host:port/database?options')
// 关闭连接
conn.close()

参数:

  • mongodb:标识标准连接格式。
  • username:用户名,可选。
  • password:密码,可选。
  • host:域名或IP地址。
  • port:端口,可选,默认为27017端口。
  • database:要连接的数据库。
  • options:选项,可选。

示例:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 引入模块
const mongoose = require('mongoose');
// 建立连接
const conn = mongoose.createConnection('mongodb://127.0.0.1:27017/test');
// 执行操作
async function execExample() {
try {
// 执行CRUD操作
} catch (err) {
// 打印错误
console.error("执行失败: " + err);
} finally {
// 关闭连接
conn.close();
}
}
execExample();

回调事件:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 建立连接失败时执行
conn.on('error', err => console.log('error: ' + err));
// 建立连接时和重新连接时执行,可能会执行多次
conn.on('connected', () => console.log('connected'));
// 建立连接时和重新连接时执行,可能会执行多次
conn.on('open', () => console.log('open'));
// 丢失连接时和关闭连接时执行,可能会执行多次
conn.on('disconnected', () => console.log('disconnected'));
// 重新连接时执行
conn.on('reconnected', () => console.log('reconnected'));
// 关闭连接时执行
conn.on('disconnecting', () => console.log('disconnecting'));
// 关闭连接时执行
conn.on('close', () => console.log('close'));

如果只有一个数据库,建议使用独立连接。

4 模式(Schema)

4.1 概念

使用模式映射集合,模式中属性的模式类型对应文档中属性的类型。

4.2 模式类型

模式类型用于定义模型中字段的数据类型,可以自定义类型。

语法:

js
1
2
3
4
5
6
new mongoose.Schema({
属性名: {
type: 模式类型,
选项名: 选项值
}
})

如果没有选项,可以简写:

js
1
2
3
new mongoose.Schema({
属性名: 模式类型
})

常见模式类型:

  • String:字符串类型。
  • Number:数字类型。
  • Date:日期类型。
  • Buffer:二进制数据类型。
  • Boolean:布尔类型。
  • Mixed:混合类型。
  • ObjectId:对象ID类型,通常用于唯一标识符。
  • Array:数组类型。

特殊模式类型:

  • Map:映射类型。
  • UUID:通用唯一标识符类型。
  • BigInt:大整数类型。
  • Double:64位浮点数类型。
  • Int32:32位整数类型。
  • Decimal128:128位浮点数类型。

4.3 模式类型选项

模式类型选项用于对模式类型进行处理和验证,可以同时设置多个选项,也可以自定义选项。

4.3.1 required

适用于所有模式类型。

类型为布尔值或函数,支持返回错误消息。

作用是指定是否必填,默认为false,true表示必填,false表示非必填。

示例:

js
1
2
3
const userSchema = new mongoose.Schema({
name: { type: String, required: [true, '属性不能为空: name'] }
});

4.3.2 default

适用于所有模式类型。

类型为任何值或函数。

作用是指定字段默认值,默认为null。

示例:

js
1
2
3
const userSchema = new mongoose.Schema({
age: { type: Number, default: 18 }
});

4.3.3 select

适用于所有模式类型。

类型为布尔值。

作用是指定是否在查询时默认返回字段,默认为true,true表示返回,false表示不返回。

示例:

js
1
2
3
const userSchema = new mongoose.Schema({
password: { type: String, select: false }
});

4.3.3 validate

适用于所有模式类型。

类型为对象。

作用是指定字段的验证对象。

示例:

js
1
2
3
4
5
6
7
8
9
10
11
12
const emailValidator = {
validator: function(v) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
},
message: props => `邮箱地址错误: ${props.value}`
};
const userSchema = new mongoose.Schema({
email: {
type: String,
validate: emailValidator
}
});

5 模型(Model)

模型是从模式编译而来的奇特构造函数,负责管理文档。

文档是模型的实例,通过模型创建文档。

语法:

js
1
const 模型 = 连接实例.model(模型名, 模式对象实例, 集合名)

说明:

  • 全局连接和独立连接的连接实例不一样。
  • 集合名可以省略,默认使用模型名的小写复数。

根据连接方式的不同,使用模型的方式也不同:

在全局连接中使用模型:

js
1
2
3
4
5
6
// 建立连接
mongoose.connect('mongodb://127.0.0.1:27017/test');
// 创建模式
const userSchema = new mongoose.Schema({ name: String, age: Number });
// 创建模型,集合名为users
const UserModel = mongoose.model('User', userSchema);

在独立连接中使用模型:

js
1
2
3
4
5
6
// 建立连接
const conn = mongoose.createConnection('mongodb://127.0.0.1:27017/test');
// 创建模式
const userSchema = new mongoose.Schema({ name: String, age: Number });
// 创建模型,集合名为users
const UserModel = conn.model('User', userSchema);

6 文档(Document)

6.1 插入

插入文档:

js
1
2
3
4
5
6
7
8
9
10
async function execInsert() {
try {
await UserModel.insertOne({ name: "张三" });
} catch (err) {
console.error("执行失败: " + err);
} finally {
mongoose.connection.close();
}
}
execInsert();

6.2 查询

查询文档:

js
1
2
3
4
5
6
7
8
9
10
11
async function execSelect() {
try {
let user = await UserModel.findOne({ name: "张三" });
console.log(user);
} catch (err) {
console.error("执行失败: " + err);
} finally {
mongoose.connection.close();
}
}
execSelect();

6.3 删除

删除文档:

js
1
2
3
4
5
6
7
8
9
10
async function execDelete() {
try {
await UserModel.deleteOne({ name: "张三" });
} catch (err) {
console.error("执行失败: " + err);
} finally {
mongoose.connection.close();
}
}
execDelete();

6.4 更新

更新文档:

js
1
2
3
4
5
6
7
8
9
10
async function execUpdate() {
try {
await UserModel.updateOne({ name: "张三" }, { name: "李四" });
} catch (err) {
console.error("执行失败: " + err);
} finally {
mongoose.connection.close();
}
}
execUpdate();

在执行操作时,建议通过await在async方法中执行,保证执行顺序,避免异步带来的问题。

6.5 查询优化

6.5.1 排序

使用sort()方法实现排序:

js
1
2
3
4
5
6
7
8
9
10
11
12
async function execSelect() {
try {
let users = await UserModel.find({ age: { $gt: 10 } })
.sort({ age: 1 });
console.log(users);
} catch (err) {
console.error("执行失败: " + err);
} finally {
mongoose.connection.close();
}
}
execSelect();

6.5.2 分页

使用skip()方法和limit()方法实现分页:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
async function execSelect() {
try {
let users = await UserModel.find({ age: { $gt: 10 } })
.skip(1)
.limit(2);
console.log(users);
} catch (err) {
console.error("执行失败: " + err);
} finally {
mongoose.connection.close();
}
}
execSelect();

6.5.3 投影

使用select()方法实现投影:

js
1
2
3
4
5
6
7
8
9
10
11
12
async function execSelect() {
try {
let users = await UserModel.find({ age: { $gt: 10 } })
.select({ _id: 0, name: 1, age: 1 });
console.log(users);
} catch (err) {
console.error("执行失败: " + err);
} finally {
mongoose.connection.close();
}
}
execSelect();

6.5.4 聚合

使用aggregate()方法实现聚合:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function execSelect() {
try {
let users = await UserModel.aggregate([
{ $match: { age: { $gt: 10 } } },
{ $project: { _id: 0, name: 1, age: 1 }},
{ $sort: { age: 1 } },
{ $skip: 1 },
{ $limit: 2 }
]);
console.log(users);
} catch (err) {
console.error("执行失败: " + err);
} finally {
mongoose.connection.close();
}
}
execSelect();

7 事务

使用事务需要开启副本集,否则会报错。

根据连接方式的不同,使用事务的方式也不同:

在全局连接中使用事务:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 引入模块
const mongoose = require('mongoose');
// 建立连接
mongoose.connect('mongodb://127.0.0.1:27017/test');
// 创建模式
const userSchema = new mongoose.Schema({ name: String, age: Number });
// 创建模型,集合名为users
const UserModel = mongoose.model('User', userSchema);
// 执行操作
async function execSelect() {
// 创建会话
const session = await mongoose.startSession();
try {
// 开始事务
await session.startTransaction();
// 执行操作
await UserModel.insertOne({ name: "张三" }, { session: session });
await UserModel.insertOne({ name: "李四" }, { session: session });
// 模拟错误
if (user) {
throw new Error('操作异常');
}
// 提交事务
await session.commitTransaction();
} catch (err) {
console.error("执行失败: " + err);
// 回滚事务
await session.abortTransaction();
} finally {
// 结束事务
session.endSession();
// 关闭连接
mongoose.connection.close();
}
}
execSelect();

在独立连接中使用事务:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 引入模块
const mongoose = require('mongoose');
// 建立连接
const conn = mongoose.createConnection('mongodb://127.0.0.1:27017/test');
// 创建模式
const userSchema = new mongoose.Schema({ name: String, age: Number });
// 创建模型,集合名为users
const UserModel = conn.model('User', userSchema);
// 执行操作
async function execSelect() {
// 创建会话
const session = await conn.startSession();
try {
// 开始事务
await session.startTransaction();
// 执行操作
await UserModel.insertOne({ name: "张三" }, { session: session });
await UserModel.insertOne({ name: "李四" }, { session: session });
// 模拟错误
if (user) {
// ReferenceError: user is not defined
throw new Error('操作异常');
}
// 提交事务
await session.commitTransaction();
} catch (err) {
console.error("执行失败: " + err);
// 回滚事务
await session.abortTransaction();
} finally {
// 结束事务
session.endSession();
// 关闭连接
conn.close();
}
}
execSelect();

8 钩子

钩子是十分重要的函数,它可以用来控制对数据的访问以及修改,包括数据的CURD等操作。

钩子函数主要分为两种类型:

  • 预处理钩子(Pre Hooks):在执行各种数据库操作之前执行。
  • 后处理钩子(Post Hooks):在执行各种数据库操作之后执行。

在使用钩子函数时,可以定义多个钩子函数,并以串联的方式依次执行,以达到特定的业务逻辑目的。

需要在创建模型前设置:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 创建模式
const userSchema = new mongoose.Schema({ name: String, age: Number });
// 预处理钩子函数A
userSchema.pre('deleteOne', (next) => {
console.log('预处理钩子函数A');
next();
});
// 预处理钩子函数B
userSchema.pre('deleteOne', (next) => {
console.log('预处理钩子函数B');
next();
});
// 后处理钩子函数A
userSchema.post('deleteOne', (docs, next) => {
console.log('后处理钩子函数A docs: ' + JSON.stringify(docs));
next();
});
// 后处理钩子函数B
userSchema.post('deleteOne', (docs, next) => {
console.log('后处理钩子函数B docs: ' + JSON.stringify(docs));
next();
});
// 创建模型,集合名为users
const UserModel = mongoose.model('User', userSchema);

评论