摘要:本文主要学习了如何在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 连接
有两种方式建立连接:
语法:
js1 2 3 4
| mongoose.connect('mongodb://username:password@host:port/database?options')
mongoose.connection.close()
|
参数:
- mongodb:标识标准连接格式。
- username:用户名,可选。
- password:密码,可选。
- host:域名或IP地址。
- port:端口,可选,默认为27017端口。
- database:要连接的数据库。
- options:选项,可选。
示例:
js1 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 { } catch (err) { console.error("执行失败: " + err); } finally { mongoose.connection.close(); } } execExample();
|
使用connect()方法返回的Promise对象处理连接错误:
js1 2 3 4 5
| const mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1:27017/test') .catch(err => console.log("连接失败: " + err));
|
回调事件:
js1 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'));
|
语法:
js1 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:选项,可选。
示例:
js1 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 { } catch (err) { console.error("执行失败: " + err); } finally { conn.close(); } } execExample();
|
回调事件:
js1 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 模式类型
模式类型用于定义模型中字段的数据类型,可以自定义类型。
语法:
js1 2 3 4 5 6
| new mongoose.Schema({ 属性名: { type: 模式类型, 选项名: 选项值 } })
|
如果没有选项,可以简写:
js1 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表示非必填。
示例:
js1 2 3
| const userSchema = new mongoose.Schema({ name: { type: String, required: [true, '属性不能为空: name'] } });
|
4.3.2 default
适用于所有模式类型。
类型为任何值或函数。
作用是指定字段默认值,默认为null。
示例:
js1 2 3
| const userSchema = new mongoose.Schema({ age: { type: Number, default: 18 } });
|
4.3.3 select
适用于所有模式类型。
类型为布尔值。
作用是指定是否在查询时默认返回字段,默认为true,true表示返回,false表示不返回。
示例:
js1 2 3
| const userSchema = new mongoose.Schema({ password: { type: String, select: false } });
|
4.3.3 validate
适用于所有模式类型。
类型为对象。
作用是指定字段的验证对象。
示例:
js1 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)
模型是从模式编译而来的奇特构造函数,负责管理文档。
文档是模型的实例,通过模型创建文档。
语法:
js1
| const 模型 = 连接实例.model(模型名, 模式对象实例, 集合名)
|
说明:
- 全局连接和独立连接的连接实例不一样。
- 集合名可以省略,默认使用模型名的小写复数。
根据连接方式的不同,使用模型的方式也不同:
在全局连接中使用模型:
js1 2 3 4 5 6
| mongoose.connect('mongodb://127.0.0.1:27017/test');
const userSchema = new mongoose.Schema({ name: String, age: Number });
const UserModel = mongoose.model('User', userSchema);
|
在独立连接中使用模型:
js1 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 });
const UserModel = conn.model('User', userSchema);
|
6 文档(Document)
6.1 插入
插入文档:
js1 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 查询
查询文档:
js1 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 删除
删除文档:
js1 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 更新
更新文档:
js1 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()方法实现排序:
js1 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()方法实现分页:
js1 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()方法实现投影:
js1 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()方法实现聚合:
js1 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 事务
使用事务需要开启副本集,否则会报错。
根据连接方式的不同,使用事务的方式也不同:
在全局连接中使用事务:
js1 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 });
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();
|
在独立连接中使用事务:
js1 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 });
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) { 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):在执行各种数据库操作之后执行。
在使用钩子函数时,可以定义多个钩子函数,并以串联的方式依次执行,以达到特定的业务逻辑目的。
需要在创建模型前设置:
js1 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 });
userSchema.pre('deleteOne', (next) => { console.log('预处理钩子函数A'); next(); });
userSchema.pre('deleteOne', (next) => { console.log('预处理钩子函数B'); next(); });
userSchema.post('deleteOne', (docs, next) => { console.log('后处理钩子函数A docs: ' + JSON.stringify(docs)); next(); });
userSchema.post('deleteOne', (docs, next) => { console.log('后处理钩子函数B docs: ' + JSON.stringify(docs)); next(); });
const UserModel = mongoose.model('User', userSchema);
|
条