Skip to content

MongoDB(文档型数据库)

MongoDB Database(社区版)

MongoDB 是一个面向文档的 NoSQL 数据库管理系统,它以 JSON 格式的 BSON(Binary JSON)文档来存储数据。在 MongoDB 中,数据库(Database)是一个逻辑数据容器,用于组织和存储集合;集合(Collection)是一个数据表,用于组织和存储文档;文档(Documnet)是一条详细的数据;了解基本知识详见使用文档

可下载安装 MongoDB Database (以 6.x 版本为例)作为本地开发调试使用,客户端下载地址

Windows 服务

安装 MongDB 客户端之后默认会设置为开机自动启动 MongDB 服务,若设置为开机手动启动 MongDB 服务,则需要打开“任务管理器”,选择“服务”选项,找到“MongoDB",右击“开始”。

TIP

MongoDB 默认存储数据的数据目录为 C:\Program Files\MongoDB\Server\6.0\data\ ,若需要自定义,则需要在 mongod.cfg 配置文件中修改。

命令提示符

  1. 创建 MongoDB 存储数据的数据目录

MongoDB 的默认数据目录路径是启动 MongoDB 的驱动器上的绝对路径(如 C:\data\db )。

  1. 启动 MongoDB 数据库

将 MongDB 安装路径 C:\Program Files\MongoDB\Server\6.0\bin\ 设置为环境变量之系统变量的 Path 环境变量,然后打开 cmd.exe 输入 mongod 命令。

bash
mongod

TIP

若自定义 MongoDB 数据目录路径,则需要使用 --dbpath 选项指向 y 该数据库目录。

bash
mongod --dbpath="c:\Program Files\MongoDB\Server\6.0\data\db"

MongoDB Shell

MongoDB Shell 是 MongoDB 的官方命令行工具,它允许你与 MongoDB 数据库进行交互和管理,执行各种操作,包括查询、插入、更新、删除、创建索引等,相关操作命令见使用文档

自 6.x 版本之后需要单独下载方可执行命令行,下载为一个 ZIP 压缩包,解压后将其放至与 Mongodb Database 安装路径下的 bin 文件夹同级目录中,客户端下载地址

注意

  • 使用命令行之前,需要将 MongoDB Shell 安装路径 C:\Program Files\MongoDB\Server\6.0\mongosh-1.10.6-win32-x64\bin 设置为环境变量之系统变量的 Path 环境变量,然后启动 MongDB 服务,最后再启动 MongoDB Shell 。

  • 在命令的输出包含 { ok: 0 } 的情况下, mongosh 将引发异常,并且不会从服务器返回原始输出

启动 mongosh

  • 连接到默认端口上的本地部署
bash
mongosh
# 或
mongosh "mongodb://localhost:27017"
  • 连接到非默认端口上的本地部署
bash
mongosh "mongodb://localhost:28015"
# 或
mongosh --port 28015
  • 连接到远程主机上的部署
bash
mongosh "mongodb://mongodb0.example.com:28015"
# 或
mongosh --host mongodb0.example.com --port 28015

mongosh APIs

  • 打印当前 mongosh 配置
bash
config
  • 获取当前 <property>
bash
config.get( "<property>" )
  • 修改当前 <property>
bash
config.set( "<property>", <value> )
  • 重置当前 <property> 默认值
bash
config.reset( "<property>" )
Details
属性类型默认值说明
displayBatchSizeinteger20每次光标迭代显示的项目数
enableTelemetrybooleantrue允许向 MongoDB 发送匿名跟踪和诊断数据
editorstringnull指定要在 mongosh 控制台中使用的编辑器。覆盖环境变量( EDITOR 如果已设置)
forceDisableTelemetrybooleanfalse仅在全局配置文件中可用。如果为 true,则用户无法手动启用遥测(mongosh 收集匿名聚合使用数据以改进 MongoDB 产品。 mongosh 默认情况下收集此信息,但您可以随时禁用此数据收集);禁用遥测:disableTelemetry(),mongosh 启动时禁用遥测:mongosh --nodb --eval "disableTelemetry()",启用遥测:enableTelemetry()
historyLengthinteger1000要存储在 mongosh REPL 的历史记录文件中的项目数
inspectCompactinteger 或 boolean3mongosh 在一行上输出的内部元素的级别。短数组元素也被分组在一行上
inspectDepthinteger 或 Infinity6将 inspectDepth 设置为 Infinity(javascript 对象)会将所有嵌套对象打印到其完整深度
redactHistorystringremove控制 shell 历史记录中记录哪些信息。可选值为:keep(保留所有历史记录)、remove(删除包含敏感信息的行)、remove-redact(编辑敏感信息)
showStackTracesbooleanfalse控制堆栈跟踪以及错误消息的显示
snippetAutoloadbooleantrue如果为 true ,则在启动时自动加载已安装的代码段
snippetIndexSourceURLsstring链接到代码片段注册表的以分号分隔的 URL 列表
snippetRegistryURLstring使用 mongosh npm 客户端安装代码片段的 npm 注册表

mongosh.cfg

与 mongosh.exe 文件位于同一目录中。

bash
mongosh:
  displayBatchSize: 50
  inspectDepth: 20
  redactHistory: "remove-redact"

TIP

可以使用 mongosh APIs 指定的设置:

  • 覆盖 mongosh.cfg 文件中指定的设置
  • 重启后 mongosh.cfg 文件中仍保留其设置

数据库

  • 显示当前数据库
bash
# 该操作应返回 test ,这是默认数据库
db
  • 显示所有数据库
bash
show dbs
  • 连接数据库
bash
use <database>

MongoDB Compass

MongoDB Compass 是 MongoDB 官方提供的图形化工具,它提供了直观的界面来探索和操作 MongoDB 数据库,客户端下载地址

MongoDB Driver

Mongoose 是一个用于 Node.js 的 JavaScript 库,它用于在 MongoDB 数据库中进行对象建模和操作。它提供了一种非常方便的方式来定义数据模型,并且可以使用它来执行各种数据库操作,例如插入、查询、更新和删除文档。Mongoose 还支持数据验证、中间件和查询构建等功能,使得与 MongoDB 数据库的交互更加简单和高效。它通常被用于构建 Node.js 应用程序的后端部分,用于处理数据存储和访问的需求。

安装

bash
npm install mongoose

Mongoose

  • connect(uri,options):创建一个全局默认连接,通常用于单一数据库应用程序
js
const { connect } = require('mongoose')

connect('mongodb://localhost/database', {
  useNewUrlParser: true,
  useUnifiedTopology: true
})
  • createConnection(uri,options):创建多个不同的连接实例,每个连接可以连接到不同的数据库
js
const { createConnection } = require('mongoose')

// 创建第一个连接实例
const connection1 = mongoose.createConnection('mongodb://localhost/database1')

// 创建第二个连接实例
const connection2 = mongoose.createConnection('mongodb://localhost/database2')

TIP

options 配置选项:

  • useNewUrlParser: 是否启用新的 URL 字符串解析器,推荐设置为 true。
  • useUnifiedTopology: 是否启用 MongoDB 的新的服务器发现和监视引擎,推荐设置为 true。
  • autoIndex: 是否自动创建索引,默认为 true。
  • serverSelectionTimeoutMS:服务器选择超时(以毫秒为单位)
  • socketTimeoutMS:套接字超时(以毫秒为单位)
  • dbName:要连接的数据库的名称
  • user / pass:用于身份验证的用户名和密码。
  • authSource:用于身份验证的数据库名称
  • authMechanism:身份验证机制的名称
  • useCreateIndex: 使用 createIndex() 方法来创建索引,推荐设置为 true。
  • maxPoolSize: 连接池中的最大连接数量,默认为 100。
  • minPoolSize:连接池中的最小连接数量。
  • family:是使用 IPv4 还是 IPv6 进行连接。
  • bufferCommands: 用于禁用 Mongoose 的缓冲机制,默认为 true。
  • ssl:是否启用 SSL/TLS 连接 MongoDB ,默认为 false。
  • sslCA、sslCert 和 sslKey:用于 SSL/TLS 连接的证书和密钥文件的路径
  • sslPass:用于解密 SSL/TLS 私钥的密码

connect() 与 createConnection() 区别

  • connect():用于创建默认的数据库连接,通常用于单一数据库应用程序。它在全局范围内创建一个默认连接,以便您在应用程序的任何地方都可以使用它
js
const { connect, Schema, model } = require('mongoose')

// 连接到数据库
connect('mongodb://localhost/mydatabase', {
  useNewUrlParser: true,
  useUnifiedTopology: true
})

// 创建模型,与默认连接关联
const userSchema = new Schema({
  username: String,
  age: Number
})
const User = model('User', userSchema)
  • createConnection():用于创建一个新的数据库连接实例。您可以使用它来建立多个不同的连接,每个连接可以连接到不同的数据库
js
const { createConnection, Schema } = require('mongoose')

// 创建数据库连接实例
const connection1 = createConnection('mongodb://localhost/database1')
const connection2 = createConnection('mongodb://localhost/database2')

// 创建模型,与特定连接关联
const userSchema = new Schema({
  username: String,
  age: Number
})
const User1 = connection1.model('User1', userSchema)
const User2 = connection2.model('User2', userSchema)
  • Mongoose.set():用于设置全局选项,通常情况下,这些选项可以在连接数据库之前进行设置
js
// 一次设置一个
mongoose.set('autoIndex', false)

// 一次性设置多个
mongoose.set({ runValidators: false, strict: true })

支持的选项包括

  • allowDiskUse:启用 Mongoose 查询中的 allowDiskUse 选项
  • applyPluginsToChildSchemas:设置为 false 可跳过将全局插件应用于子数据库模式
  • applyPluginsToDiscriminators:设置为 true 可将全局插件应用于鉴别器架构
  • autoCreate:设置为 true 使 Mongoose 通过 conn.model() 或 mongoose.model() 创建模型时自动调用 Model.createCollection()
  • autoIndex:是否启用自动创建索引的功能
  • bufferCommands:是否启用缓冲命令的功能
  • bufferTimeoutMS:设置 Mongoose 缓冲在引发错误之前等待的最长时间,默认为 10s
  • cloneSchemas:是否克隆数据库模式
  • debug:用于启用 Mongoose 调试模式
  • id:是否为每个文档自动生成一个 _id 字段
  • timestamps.createdAt.immutable:是否允许更改文档的 createdAt 时间戳字段
  • maxTimeMS:如果设置,则将 maxTimeMS 附加到每个查询
  • objectIdGetter:Mongoose 向 MongoDB ObjectId 添加了一个名为 _id 的 getter,该 getter 会返回该值以方便填充,默认为 true
  • overwriteModels:设置为 true 默认在调用 mongoose.model() 时覆盖同名模型,而不是抛出 OverwriteModelError
  • returnOriginal:如果为 false,则将 findOneAndUpdate()、findByIdAndUpdate 和 findOneAndReplace() 的默认 returnOriginal 选项更改为 false
  • runValidators:是否在执行更新操作时运行验证器
  • sanitizeFilter:设置为 true 可以通过将任何具有名称以 $ 开头的属性的嵌套对象包装在 $eq 中来针对查询选择器注入攻击启用查询过滤器清理
  • selectPopulatedPaths:是否自动选择 populated 路径
  • strict:是否应该强制执行严格模式
  • strictQuery:是否启用严格查询模式
  • toJSON:是否启用默认的 toJSON 转换
  • toObject:是否启用默认的 toObject 转换
  • Mongoose.get():用于获取模型实例的属性值

  • Mongoose.Decimal128:用于存储具有高精度要求的十进制数值

  • Mongoose.Mixed:在同一字段中存储多种不同的数据类型

  • Mongoose.ObjectId:用于在 MongoDB 数据库中创建文档之间的关联

  • Mongoose.Types:用于定义 Schema 数据库模式中指定字段的数据类型

  • Mongoose.version:用于获取 Mongoose 的版本号

Connection(连接)

  • connection.on(eventName, callback):监视当前 Node.js 应用程序与 MongoDB 数据库之间的连接状态
js
const { connect, connection } = require('mongoose')

connect('mongodb://localhost/your-database-name', {
  useNewUrlParser: true,
  useUnifiedTopology: true
})

connection.on('connecting', () => {
  console.log('连接正在进行中...')
})

connection.once('connected', () => {
  console.log('数据库连接成功!')
})

connection 事件类型

  • connecting:当 Mongoose 开始尝试连接 MongoDB 数据库时触发。这表示连接操作已经启动,但尚未成功
  • connected:当成功连接到 MongoDB 数据库时触发。这表示连接操作已经成功完成
  • open:与 connected 事件类似,当成功连接到 MongoDB 数据库时触发。通常可以使用 connected 事件替代,但 open 事件允许你访问底层的 MongoDB 连接对象
  • disconnecting:当 Mongoose 开始断开与 MongoDB 数据库的连接时触发。这表示断开连接操作已经启动,但尚未完成
  • disconnected:当与 MongoDB 数据库的连接断开时触发。这表示连接已经断开,无法进行数据库操作
  • close:与 disconnected 事件类似,当与 MongoDB 数据库的连接关闭时触发。通常可以使用 disconnected 事件替代,但 close 事件允许你访问底层的 MongoDB 连接对象
  • reconnected:当与 MongoDB 数据库的连接重新建立时触发。如果连接在某个时刻断开,但之后重新建立,将触发此事件
  • error:当连接过程中发生错误时触发。这表示连接尝试失败
  • connection.close([callback]):用于手动断开与数据库的连接

  • connection.readyState:一个表示连接状态的整数,可选值:0(disconnected)、1(connected)、2(connecting)、3(disconnecting)

  • connection.useDb(dbName, options):用于在同一连接上切换到不同的数据库

Schema(数据库模式)

数据库模式是关于数据库中表格、字段、关系和约束的规范和描述。它定义了数据的结构和组织方式,包括表格的名称、列的数据类型、主键、外键、验证规则以及默认值等。数据库模式有助于确保数据的一致性和完整性。

js
const { Schema } = require('mongoose')

const userSchema = new Schema({
  name: String,
  sex: Array,
  age: Number,
  email: String,
  phone: Number
})

TIP

默认情况下,MongoDB 会向模型中添加一个 _id: mongoose.Types.ObjectId 字段

实例方法 Methods

在 Schema 上定义的方法,这些方法可以在文档实例上调用,用于执行各种自定义操作。Schema methods 通常用于在模型的实例级别上处理数据和业务逻辑。

  • 方式一
js
const userSchema = new mongoose.Schema(
  {
    name: String,
    age: Number
  },
  // 定义 Schema method
  {
    methods: {
      getInfo() {
        return `姓名:${this.name}, 年龄:${this.age}`
      }
    }
  }
)

// 创建 User 模型
const User = mongoose.model('User', userSchema)

// 创建一个 User 实例
const user = new User({ name: 'John', age: 30 })

// 调用 Schema method
const userInfo = user.getInfo()
console.log(userInfo) // 输出:姓名:John, 年龄:30
  • 方式二
js
const userSchema = new mongoose.Schema({
  name: String,
  age: Number
})

// 定义 Schema method
userSchema.methods.getInfo = function () {
  return `姓名:${this.name}, 年龄:${this.age}`
}

// 创建 User 模型
const User = mongoose.model('User', userSchema)

// 创建一个 User 实例
const user = new User({ name: 'John', age: 30 })

// 调用 Schema method
const userInfo = user.getInfo()
console.log(userInfo) // 输出:姓名:John, 年龄:30

静态函数 Static

在 Schema 上定义的方法,这些方法不会作用于单个文档实例,而是作用于整个模型本身。要使用静态方法,只需在模型上调用它,就像调用普通的 JavaScript 方法一样。这些静态方法可以用于执行与数据库相关的操作,例如查询、更新或删除文档。

  • 方法一
js
const userSchema = new mongoose.Schema(
  {
    name: String,
    age: Number
  },
  // 定义 Schema static
  {
    static: {
      findByName(name, callback) {
        return this.find({ name }, callback)
      }
    }
  }
)

// 创建 User 模型
const User = mongoose.model('User', userSchema)

// 使用静态方法
User.findByName('John')
  .then((users) => console.log('通过静态方法查找的用户:', users))
  .catch((err) => console.error(err))
  • 方法二
js
const userSchema = new mongoose.Schema({
  name: String,
  age: Number
})

// 定义 Schema static
userSchema.statics.findByName = function (name, callback) {
  return this.find({ name }, callback)
}

// 创建 User 模型
const User = mongoose.model('User', userSchema)

// 使用静态方法
User.findByName('John')
  .then((users) => console.log('通过静态方法查找的用户:', users))
  .catch((err) => console.error(err))
  • 方法三
js
const userSchema = new mongoose.Schema({
  name: String,
  age: Number
})

// 定义 Schema static
userSchema.statics('findByName', function (name, callback) {
  return this.find({ name }, callback)
})

// 创建 User 模型
const User = mongoose.model('User', userSchema)

// 使用静态方法
User.findByName('John')
  .then((users) => console.log('通过静态方法查找的用户:', users))
  .catch((err) => console.error(err))

查询方法 Query

在查询文档时使用自定义的查询方法。这些自定义方法可以与查询链式调用一起使用,以便更轻松地构建和执行查询。

  • 方法一
js
const animalSchema = new Schema(
  { name: String, type: String },
  {
    query: {
      byName(name) {
        return this.where({ name: new RegExp(name, 'i') })
      }
    }
  }
)

const Animal = mongoose.model('Animal', animalSchema)

Animal.find()
  .byName('fido')
  .exec((err, animals) => {
    console.log(animals)
  })

Animal.findOne()
  .byName('fido')
  .exec((err, animal) => {
    console.log(animal)
  })
  • 方法二
js
const animalSchema = new Schema({ name: String, type: String })

animalSchema.query.byName = function (name) {
  return this.where({ name: new RegExp(name, 'i') })
}

const Animal = mongoose.model('Animal', animalSchema)

Animal.find()
  .byName('fido')
  .exec((err, animals) => {
    console.log(animals)
  })

Animal.findOne()
  .byName('fido')
  .exec((err, animal) => {
    console.log(animal)
  })

版本键

自定义版本键的名称,默认版本键名称是 __v ;若不需要显示版本名称,可设置为 fasle

js
const { Schema } = require('mongoose')

const userSchema = new Schema(
  {
    name: String
  },
  {
    versionKey: false
  }
)

时间戳

是否自动为文档添加 createdAt 和 updatedAt 字段,用于记录文档的创建和更新时间。

js
const { Schema } = require('mongoose')

const userSchema = new Schema(
  {
    name: String
  },
  {
    timestamps: true
  }
)

中间件

中间件(也称为前置钩子和后置钩子)是在异步函数执行期间传递控制权的函数。中间件在数据库模式级别指定,对于编写插件很有用。

TIP

  • 前置中间件(pre middleware):前置中间件允许您在执行数据库操作之前执行某些操作。这可以用于执行预处理、验证、日志记录等任务
js
// 在保存文档之前执行的前置中间件
userSchema.pre('save', function (next) {
  console.log('在保存文档之前执行的操作')
  next()
})
  • 后置中间件(post middleware):后置中间件允许您在执行数据库操作之后执行某些操作。这可以用于执行后处理、日志记录、发送通知等任务
js
// 在保存文档后执行的后置中间件
userSchema.post('save', function (doc) {
  console.log('在保存文档后执行的操作')
})
文档中间件

在 Mongoose 中,一个文档即是一个数据模型的实例。在文档中间件函数中,this 始终指向文档。在访问数据模型时使用 this.constructor

  • Document.validate():在该文档中执行注册中间件规则
js
doc.validate({ validateModifiedOnly: false, pathsToSkip: ['name', 'email'] })
  • Model.save():如果 document.isNew 是 true ,则通过将新文档插入数据库来保存此文档,或者如果为 false ,则 isNew 仅使用修改后的路径发送 updateOne 操作;与数据模型中的新增文档与更新文档相同

  • Document.updateOne():与 Model.updateOne 中相同

  • Document.deleteOne():与 Model.deleteOne 中相同

  • Document.init():初始化文档,从 mongodb 返回文档后在内部调用。

数据模型中间件
  • Model.insertMany():用于一次性插入多个文档
聚合中间件
  • Model.aggregate():用于执行聚合操作,在查询数据时进行多个数据处理步骤,如筛选、分组、排序、计算等。聚合操作通常用于处理大量数据,以获取所需的统计信息或变换数据。
查询中间件
  • Query.deleteMany():用于删除满足查询条件的多个文档

  • Query.deleteOne():用于删除满足查询条件的单个文档

  • Query.find():用于执行查询操作并返回匹配查询条件的多个文档

  • Query.findOne():用于执行查询操作并返回匹配查询条件的单个文档

  • Query.findOneAndDelete():用于执行查询操作并删除满足查询条件的第一个文档

  • Query.findOneAndRemove():用于执行查询操作并删除满足查询条件的第一个文档

  • Query.findOneAndReplace():用于执行查询操作并替换满足查询条件的第一个文档

  • Query.findOneAndUpdate():用于执行查询操作并更新满足查询条件的第一个文档

  • Query.replaceOne():用于执行查询操作并替换满足查询条件的第一个文档

  • Query.updateOne():用于更新满足查询条件的第一个文档

  • Query.updateMany():用于更新满足查询条件的所有文档

  • validate:默认情况下,更新文档不执行数据类型校验,如果要强制执行数据类型校验,可以传递 { runValidators: true } 选项

SchemaType

Mongoose Schema 是 Mongoose Model 的配置对象,而 SchemaType 数据类型是 Mongoose Schema 的单个属性的配置对象。

js
const { Schema } = require('mongoose')

const userSchema = new Schema({
  name: {
    type: String,
    required: true
  },
  sex:{
    type:Array,
    enum: ['男', '女', '保密'],
  }
  age: {
    type: Number,
    default: 25
  },
  email: {
    type: String,
    unique: true
  },
  phone: {
    type: Number,
    validate: {
      validator: function (value) {
        return value.length === 11
      },
      message: '手机号必须是11位'
    }
  }
})

type 属性

type 是 Monooges Schema 中的特殊属性。当 Mongoose 在 Schema 中找到名为 type 的嵌套属性时,Mongoose 会假定该字段定义 SchemaType 数据类型。

type 属性值

  • String:字符串类型

  • Number:数值类型

  • Boolean:布尔类型

  • Array:数组类型

  • Buffer:二进制数据类型

  • Date:日期类型

  • Schema.Types.ObjectId:对象 ID

  • Schema.Types.Mixed:混合类型

  • Schema.Types.UUID:唯一 ID

  • BigInt:大整数类型

  • Schema.Types.Map:映射类型

  • Schema:嵌套文档

  • Schema.Types.Decimal128:高精度的十进制数值类型

公共属性

  • default:设置字段的默认值
js
const userSchema = new mongoose.Schema({
  status: { type: String, default: 'active' },
  registrationDate: { type: Date, default: Date.now }
})
  • immutable:字段的值在文档创建后是否可修改
js
const userSchema = new mongoose.Schema({
  username: {
    type: String,
    immutable: true // 一旦设置后,无法更改用户名
  }
})
  • required:创建一个验证器,指定字段是否是必需的
js
const breakfastSchema = new Schema({
  bacon: {
    type: Number,
    required: [true, 'Why no bacon?']
  },
  drink: {
    type: String,
    required: function () {
      // 不可使用箭头函数,否则 this 无效
      return this.bacon > 3
    }
  }
})
  • index:指定是否在字段上创建索引
js
const userSchema = new mongoose.Schema({
  name: {
    type: String,
    index: true // 创建默认升序索引
  },
  age: {
    type: Number,
    index: { unique: true, parse: true } // 创建唯一索引
  }
})
  • unique:设置字段的值是否必须在集合中是唯一的
js
const userSchema = new mongoose.Schema({
  email: {
    type: String,
    unique: true
  }
})
  • sparse:当创建索引时,该属性允许在文档中缺少字段的情况下不会引发唯一索引冲突
js
const userSchema = new mongoose.Schema({
  email: {
    type: String,
    unique: true,
    sparse: true // 允许文档中缺少 email 字段
  }
})
  • select:指定在从数据库检索文档时是否包括此字段
js
const userSchema = new mongoose.Schema({
  password: {
    type: String,
    select: false // 在查询时排除密码字段
  }
})
  • ref:用于在文档之间创建关联
js
const postSchema = new mongoose.Schema({
  title: String,
  content: String
})

const userSchema = new mongoose.Schema({
  username: String,
  posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }]
})
  • get():添加一个 getter
js
const mySchema = new mongoose.Schema({
  price: {
    type: Number,
    get: (v) => Math.round(v) // 将价格四舍五入
  }
})
  • set():添加一个 setter
js
const mySchema = new mongoose.Schema({
  price: {
    type: Number,
    set: (v) => Math.round(v) // 将价格四舍五入
  }
})
  • validate():自定义一个验证器
js
const userSchema = new Schema({
  phone: {
    type: String,
    validate: {
      validator: function (v) {
        return /\d{3}-\d{3}-\d{4}/.test(v)
      },
      message: (props) => `${props.value} is not a valid phone number!`
    },
    required: [true, 'User phone number required']
  }
})

String 专有属性

  • lowercase:是否总是调用 .toLowerCase()
js
const userSchema = new mongoose.Schema({
  countryCode: {
    type: String,
    lowercase: true // 将值强制转换为小写
  }
})
  • uppercase:是否总是调用 .toUpperCase()
js
const userSchema = new mongoose.Schema({
  countryCode: {
    type: String,
    uppercase: true // 将值强制转换为大写
  }
})
  • trim:是否总是调用 .trim()
js
const userSchema = new mongoose.Schema({
  username: {
    type: String,
    trim: true
  }
})
  • enum:创建一个验证器,用于限制字段的值必须是指定的枚举值之一
js
const breakfastSchema = new Schema({
  drink: {
    type: String,
    enum: {
      values: ['Coffee', 'Tea'],
      message: '{VALUE} is not supported'
    }
  }
})
  • match:创建一个验证器,用于检查值是否与给定的正则表达式匹配
js
const userSchema = new mongoose.Schema({
  email: {
    type: String,
    match: [/^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/, 'email格式不正确']
  }
})
  • minLength:创建一个验证器,用于检查值长度是否不小于给定的数字
js
const userSchema = new mongoose.Schema({
  username: {
    type: String,
    minlength: [4, '最小长度为4个字符']
  }
})
  • maxLength:创建一个验证器,用于检查值长度是否不大于给定数字
js
const postSchema = new mongoose.Schema({
  content: {
    type: String,
    maxlength: [200, '最大长度为200个字符']
  }
})

Number 专有属性

  • min:创建一个验证器,用于检查值是否大于或等于给定的最小值
js
const userSchema = new mongoose.Schema({
  age: {
    type: Number,
    min: [18, '最小年龄为18']
  }
})
  • max:创建一个验证器,用于检查值是否小于或等于给定的最大值
js
const userSchema = new mongoose.Schema({
  age: {
    type: Number,
    max: [100, '最大年龄为100']
  }
})

Date 专有属性

  • min:创建一个验证器,用于检查值是否大于或等于给定的最小值
js
const userSchema = new mongoose.Schema({
  birthday: {
    type: Date,
    min: ['1950-01-01', '最小日期为1950年1月1日']
  }
})
  • max:创建一个验证器,用于检查值是否小于或等于给定的最大值
js
const userSchema = new mongoose.Schema({
  birthday: {
    type: Date,
    max: ['2005-12-31', '最大日期为2005年12月31日']
  }
})
  • expires:用于指定文档的过期时间,一旦超过有效期,文档将自动从集合中删除
js
const sessionSchema = new mongoose.Schema({
  user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  expiresAt: { type: Date, expires: '1d' } // 一天后过期
})

Model(数据模型)

在数据库设计中,数据模型是描述数据实体、它们之间的关系以及数据如何存储和访问的抽象表示。数据模型用于定义数据的结构,包括表格、字段、索引等,以及对数据进行 CRUD(创建、读取、更新、删除)操作。

js
const User = mongoose.model('User', userSchema)

注意

  • 在 Mongoose 中,一个数据模型的实例即是一个文档

  • mongoose.model() 第一个参数是数据模型所针对的集合单数名称,Mongoose 会自动查找数据模型名称的复数小写名称(即:数据模型 User 用于数据库中的 users 集合)

新增文档

  • Document.save()
js
const newUser = new User({
  username: 'john_doe',
  email: 'john@example.com',
  age: 25
})

newUser.save((err, savedUser) => {
  if (err) {
    console.error('插入文档时出错:', err)
  } else {
    console.log('文档已成功插入到数据库:', savedUser)
  }
})
  • Model.create()
javascript
User.create({
  username: 'john_doe',
  email: 'john@example.com',
  age: 25
})
  .then((savedUser) => {
    console.log('文档已成功插入到数据库:', savedUser)
  })
  .catch((err) => {
    console.error('插入文档时出错:', err)
  })
  • Model.insertMany()
javascript
User.insertMany({
  username: 'john_doe',
  email: 'john@example.com',
  age: 25
})
  .then((savedUser) => {
    console.log('文档已成功插入到数据库:', savedUser)
  })
  .catch((err) => {
    console.error('插入文档时出错:', err)
  })

查询文档

  • Model.find(query, projection, options)

用于查找满足指定查询条件的多个文档。默认返回一个 Promise 对象。

js
User.find({}, { username: 1, avatar: 0 })
  .then((users) => console.log('匹配的用户:', users))
  .catch((err) => console.error('查询文档时出错:', err))
  • Model.findOne(query, projection, options)

用于查找满足查询条件的单个文档。如果多个文档满足条件,它将返回第一个匹配的文档。默认返回一个 Promise 对象。

js
User.findOne({
  $or: [{ username: { $eq: 'john_doe' } }, { username: { $regex: 'john' } }]
})
  .then((user) => console.log('第一个匹配的用户:', user))
  .catch((err) => console.error('查询文档时出错:', err))

query 运算符

操作符说明
$eq匹配等于指定值的值
$gt匹配大于指定值的值
$gte匹配大于或等于指定值的值
$lt匹配小于指定值的值
$lte匹配小于或等于指定值的值
$ne匹配所有不等于指定值的值
$in匹配数组中指定的任何值
$nin与数组中指定的值均不匹配
$all匹配包含查询中指定的所有元素的数组
$and使用逻辑 AND 连接查询子句会返回与两个子句的条件匹配的所有文档
$or使用逻辑 OR 连接查询子句会返回与任一子句的条件匹配的所有文档
$nor使用逻辑 NOR 连接查询子句会返回无法匹配两个子句的所有文档
$not反转查询表达式的效果并返回与查询表达式不匹配的文档
$regex选择值与指定正则表达式匹配的文档

projection 参数

参数说明
<field>: <1 or true>指定字段的包含。若指定非零整数,则视为 true 。
<field>: <0 or false>指定字段的排除项。

链式查询

Model.find() 必须在最后链式调用 exec() 执行查询,返回一个 Promise 对象获取结果。

方法说明
Query.select()指定要包括或排除的文档字段(也称为查询“投影”)
Query.slice()指定数组的 $slice 投影(数组切分)
Query.sort()设置排序顺序,如果传递是一个对象,则允许的值为 asc desc ascending descending 1-1;如果传递是一个字符串,则必须是以空格分隔的路径名列表。每个路径的排序顺序是升序的,除非路径名带有前缀 - ,该路径名将被视为降序(数组排序)
Query.skip()跳过文档
Query.limit()指定查询将返回的最大文档数。数
Query.populate()用于填充(或联接)关联文档。在查询结果中包含与查询文档关联的其他文档的数据,而不仅仅是关联文档的 _id 引用
Query.where()操作符,必须再次链式调用 equals() gt() gte() lt() lte() ne() in() nin() all() and or() nor() regex()

find() 、where() 与 exec() 三者关系

js
User.find({ age: { $gte: 21, $lte: 65 } })
// 等同于
User.where('age').gte(21).lte(65).exec()
  • Model.find() 方法会立即执行查询,且返回是一个 Promise 对象,无须链式调用 .exec()执行查询,可直接使用 .then.catch 获取结果;若使用了链式查询,则必须必须最后链式调用 .exec() 执行查询
  • Model.where() 方法不会立即执行查询,且返回的是一个 Mongoose 查询构建器对象,必须最后链式调用 .exec() 执行查询返回 Promise 对象,方可使用 .then.catch 获取结果

更新文档

  • Document.save()

用于更新已存在的文档,且返回一个 Promise 对象。注意:需要首先创建一个 Model 实例,然后使用 save() 方法将该实例保存到数据库中。

js
User.findOne({ username: 'john_doe' })
  .then((user) => {
    user.email = 'new_email@example.com'
    user
      .save()
      .then((updateUser) => console.log('更新成功:', updatedUser))
      .catch((err) => console.error('保存出错:', err))
  })
  .catch((err) => console.error('查询出错:', err))
  • Model.updateOne()

用于更新匹配条件的第一个文档,且返回一个 Promise 对象。

js
User.updateOne({ name: 'John' }, { age: 30 })
  .then((res) => console.log('更新结果:', res))
  .catch((err) => console.error('更新出错:', err))
  • Model.updateMany()

用于更新匹配条件的多个文档,且返回一个 Promise 对象。

js
User.updateMany({ age: { $gte: 18 } }, { isAdult: true })
  .then((res) => console.log('更新结果:', res))
  .catch((err) => console.error('更新出错:', err))
  • Model.replaceOne()

用于完全替换满足条件的文档,而不是更新字段的部分内容。如果找到匹配的文档,它将被完全替换为提供的新文档。

js
User.replaceOne({ name: 'John' }, { name: 'New John', age: 30 })
  .then((res) => console.log('替换结果:', result))
  .catch((err) => console.error('替换出错:', err))

更新操作符

操作符说明
$currentDate将字段的值设置为当前日期,可以是日期或时间戳
$inc将字段的值递增指定的量
$min仅当指定的值小于现有字段值时,才更新字段
$max仅当指定的值大于现有字段值时,才更新字段
$mul将字段的值乘以指定的数量
$rename重命名字段
$set设置文档中域的值
$setOnInsert设置更新导致插入文档时的域值。对修改现有文档的更新操作没有影响
$unset从文档中删除指定的字段
$addToSet仅当元素在集合中尚不存在时才向数组添加元素
$pop删除数组的第一个或最后一个项目
$pull删除与指定查询匹配的所有数组元素
$push将值添加到数组中

数组操作符

运算符说明
$each修改 $push 和 $addToSet 操作符以追加多个项目以进行数组更新
$position修改 $push 操作符以指定数组中要添加元素的位置
$slice修改 $push 操作符以限制更新数组的大小
$sort修改 $push 操作符以对存储在数组中的文档重新排序

注意

对于更新文档来说,Mongoose 的默认行为是不执行数据类型校验,特别是在使用 update()、updateOne()、updateMany() 等更新方法时。这意味着您可以更新文档的字段并将它们设置为与 Schema 不匹配的数据类型,而不会引发验证错误。若要强制执行更新文档时的数据类型校验,这可以通过传递 { runValidators: true } 选项来开启。这将使 Mongoose 在执行更新操作时执行数据类型校验,如果字段的数据类型与 Schema 不匹配,将引发验证错误。

js
User.updateOne({ name: 'John' }, { age: '30' }, { runValidators: true })
  .then((res) => console.log('更新成功', result))
  .catch((err) => console.error('数据类型校验错误', err))

删除文档

  • Model.deleteMany()

用于删除满足特定条件的多个文档。

js
User.deleteMany({ age: { $gte: 18 } })
  .then((res) => console.log('删除成功', res))
  .catch((err) => console.error('删除出错:', err))
  • Model.deleteOne()

用于删除满足特定条件的单个文档。

js
User.deleteOne({ name: 'John' })
  .then((res) => console.log('删除成功', res))
  .catch((err) => console.error('删除出错:', err))

关联文档

javascript
const mongoose = require('mongoose')
const Schema = mongoose.Schema

// 文章模型
const articleSchema = new Schema({
  title: {
    type: String,
    required: true
  },
  content: {
    type: String,
    required: true
  },
  author: {
    type: Schema.Types.ObjectId, //关联User表
    ref: 'User',
    required: true
  }
})

// 用户模型
const userSchema = new Schema({
  name: {
    type: String,
    required: true
  },
  age: {
    type: Number,
    required: true
  },
  email: {
    type: String,
    required: true,
    unique: true,
    match: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  },
  phone: {
    type: String,
    required: true,
    validate: /^\d{11}$/
  },
  articles: [
    {
      type: Schema.Types.ObjectId,
      ref: 'Article'
    }
  ]
})

const Article = mongoose.model('Article', articleSchema)
const User = mongoose.model('User', userSchema)

//新增
User.findOne({ name: '张三' })
  .then((res) => {
    Article.create({
      title: '文章标题',
      content: '文章内容',
      author: res._id
    })
  })
  .then((data) => {
    console.log(data)
  })
  .catch((err) => {
    console.log(err)
  })
  .finally(() => {
    mongoose.connection.close() // 关闭数据库连接
  })

//查询
User.findById(userId)
  .populate('articles')
  .exec()
  .then((user) => {
    console.log(user.articles)
  })
  .catch((err) => {
    console.error(err)
  })

mongoose.Types.ObjectId 与 Schema.Types.ObjectId

  • mongoose.Types.ObjectId 是一个构造函数,用于手动创建 MongoDB ObjectId 实例

  • Schema.Types.ObjectId 用于在 Mongoose Schema 中定义字段类型,以及与数据库中的 ObjectId 进行映射

TypeScript

ts
const { Schema, model, connect, HydratedDocument } = require('mongoose')

// 1. Create an interface representing a document in MongoDB.
interface IUser {
  name: string
  email: string
  avatar?: string
}

// 2. Create a Schema corresponding to the document interface.
const userSchema = new Schema<IUser>({
  name: { type: String, required: true },
  email: { type: String, required: true },
  avatar: String
})

// 3. Create a Model.
const User = model<IUser>('User', userSchema)

// 4. Connect to MongoDB
connect('mongodb://127.0.0.1:27017/test')
  .then((res) => console.log('连接成功', res))
  .catch((err) => console.log('连接错误', err))

const user: HydratedDocument<IUser> = new User({
  name: 'Bill',
  email: 'bill@initech.com',
  avatar: 'https://i.imgur.com/dM7Thhn.png'
})

user.save()

console.log(user.email) // 'bill@initech.com'

ObjectId 类型

要定义 ObjectId 类型的属性,在 TypeScript 文档界面中使用应该 mongoose.Types.ObjectId ,而在 Schema 定义中使用 ObjectIdSchema.Types.ObjectId

Methods 类型

ts
const { Model, Schema, model } = require('mongoose')

interface IUser {
  firstName: string
  lastName: string
}

// Put all user instance methods in this interface:
interface IUserMethods {
  fullName(): string
}

// Create a new Model type that knows about IUserMethods...
type UserModel = Model<IUser, {}, IUserMethods>

// And a schema that knows about IUserMethods
const schema = new Schema<IUser, UserModel, IUserMethods>(
  {
    firstName: { type: String, required: true },
    lastName: { type: String, required: true }
  },
  {
    methods: {
      fullName() {
        return this.firstName + ' ' + this.lastName
      }
    }
  }
)

const User = model<IUser, UserModel>('User', schema)

const user = new User({ firstName: 'Jean-Luc', lastName: 'Picard' })
const fullName: string = user.fullName() // 'Jean-Luc Picard'

Static 类型

ts
const { Model, Schema, model } = require('mongoose')

interface IUser {
  name: string
}

interface UserModel extends Model<IUser> {
  myStaticMethod(): number
}

const schema = new Schema<IUser, UserModel>({ name: String })
schema.static('myStaticMethod', function myStaticMethod() {
  return 42
})

const User = model<IUser, UserModel>('User', schema)

const answer: number = User.myStaticMethod() // 42

TIP

Mongoose 支持自动推断类型化的静态函数,因为它是在 Schema 选项中提供的。

Query 类型

ts
const { HydratedDocument, Model, Query, Schema, model } = require('mongoose')

interface Project {
  name?: string
  stars?: number
}

interface ProjectQueryHelpers {
  byName(
    name: string
  ): QueryWithHelpers<
    HydratedDocument<Project>[],
    HydratedDocument<Project>,
    ProjectQueryHelpers
  >
}

type ProjectModelType = Model<Project, ProjectQueryHelpers>

const ProjectSchema = new Schema<
  Project,
  Model<Project, ProjectQueryHelpers>,
  {},
  ProjectQueryHelpers
>(
  {
    name: String,
    stars: Number
  },
  {
    query: {
      byName(
        this: QueryWithHelpers<
          any,
          HydratedDocument<Project>,
          ProjectQueryHelpers
        >,
        name: string
      ) {
        return this.find({ name: name })
      }
    }
  }
)

// 2nd param to `model()` is the Model class to return.
const ProjectModel = model<Project, ProjectModelType>('Project', schema)

connect('mongodb://127.0.0.1:27017/test')
  .then((res) => console.log('连接成功', res))
  .catch((err) => console.log(err))

// Equivalent to `ProjectModel.find({ stars: { $gt: 1000 }, name: 'mongoose' })`
ProjectModel.find().where('stars').gt(1000).byName('mongoose')

TIP

Mongoose 支持自动推断类型化的查询函数,因为它是在 Schema 选项中提供的。

Populate 类型

ts
const { Schema, model, Document, Types } = require('mongoose')

// `Parent` represents the object as it is stored in MongoDB
interface Parent {
  child?: Types.ObjectId
  name?: string
}
const ParentModel = model<Parent>(
  'Parent',
  new Schema({
    child: { type: Schema.Types.ObjectId, ref: 'Child' },
    name: String
  })
)

interface Child {
  name: string
}
const ChildModel = model<Child>('Child', new Schema({ name: String }))

// Populate with `Paths` generic `{ child: Child }` to override `child` path
ParentModel.findOne({})
  .populate<{ child: Child }>('child')
  .exec()
  .then((doc) => {
    console.log(doc.child.name)
  })

读书、摄影、画画、弹琴、编程