继上篇文章「Koa2+MongoDB+JWT实战--Restful API最佳实践
」后,收到许多小伙伴的反馈,表示本身对于mongoose
不怎么了解,上手感受有些难度,看官方文档又基本都是英文(宝宝内心苦,但宝宝不说)。html
为了让各位小伙伴快速上手,加深对于 mongoose 的了解,我特意结合以前的项目整理了一下关于 mongoose 的一些基础知识,这些对于实战都是颇有用的。相信看了这篇文章,必定会对你快速上手,了解使用 mongoose 有不小的帮助。前端
mongoose 涉及到的概念和模块仍是不少的,大致有下面这些:node
本篇文章并不会逐个去展开详细讲解,主要是讲述在实战中比较重要的几个模块:模式(schemas)
、模式类型(SchemaTypes)
、链接(Connections)
、模型(Models)
和联表(Populate)
。git
Mongoose
的一切都始于一个Schema
。每一个 schema 映射到 MongoDB 的集合(collection
)和定义该集合(collection)中的文档的形式。github
const mongoose = require("mongoose"); const { Schema, model } = mongoose; const userSchema = new Schema( { __v: { type: Number, select: false }, name: { type: String, required: true }, password: { type: String, required: true, select: false }, avatar_url: { type: String }, gender: { type: String, enum: ["male", "female"], default: "male", required: true }, headline: { type: String }, }, { timestamps: true } ); module.exports = model("User", userSchema);
这里的__v
是versionKey
。该 versionKey 是每一个文档首次建立时,由 mongoose 建立的一个属性。包含了文档的内部修订版。此文档属性是可配置的。默认值为__v
。若是不须要该版本号,在 schema 中添加{ versionKey: false}
便可。
使用咱们的 schema 定义,咱们须要将咱们的userSchema
转成咱们能够用的模型。也就是mongoose.model(modelName, schema)
。也就是上面代码中的:正则表达式
module.exports = model("User", userSchema);
Schemas 有几个可配置的选项,能够直接传递给构造函数或设置:mongodb
new Schema({..}, options); // or var schema = new Schema({..}); schema.set(option, value);
可用选项:数据库
autoIndex
bufferCommands
capped
collection
id
_id
minimize
read
shardKey
strict
toJSON
toObject
typeKey
validateBeforeSave
versionKey
skipVersioning
timestamps
这里我只是列举了经常使用的配置项,完整的配置项可查看官方文档https://mongoosejs.com/docs/guide.html#options
。api
这里我主要说一下versionKey
和timestamps
:数组
versionKey
(上文有提到) 是 Mongoose 在文件建立时自动设定的。 这个值包含文件的内部修订号。 versionKey 是一个字符串,表明版本号的属性名, 默认值为 __v
timestamps
选项, mongoose 会在你的 schema 自动添加 createdAt
和 updatedAt
字段, 其类型为 Date
。到这里,已经基本介绍完了Schema
,接下来看一下SchemaTypes
SchemaTypes
为查询和其余处理路径默认值,验证,getter,setter,字段选择默认值,以及字符串和数字的特殊字符。 在 mongoose 中有效的 SchemaTypes 有:
String
Number
Date
Buffer
Boolean
Mixed
ObjectId
Array
Decimal128
Map
看一个简单的示例:
const answerSchema = new Schema( { __v: { type: Number, select: false }, content: { type: String, required: true }, answerer: { type: Schema.Types.ObjectId, ref: "User", required: true, select: false }, questionId: { type: String, required: true }, voteCount: { type: Number, required: true, default: 0 } }, { timestamps: true } );
required
: 布尔值或函数,若是为 true,则为此属性添加必须的验证。default
: 任意类型或函数,为路径设置一个默认的值。若是值是一个函数,则函数的返回值用做默认值。select
: 布尔值 指定 query 的默认 projections
validate
: 函数,对属性添加验证函数。get
: 函数,使用 Object.defineProperty()
定义自定义 getterset
: 函数,使用 Object.defineProperty()
定义自定义 setteralias
: 字符串,只对mongoose>=4.10.0
有效。定义一个具备给定名称的虚拟属性,该名称能够获取/设置这个路径你能够用 schema 类型选项声明 MongoDB 的索引。
index
: 布尔值,是否在属性中定义一个索引。unique
: 布尔值,是否在属性中定义一个惟一索引。sparse
: 布尔值,是否在属性中定义一个稀疏索引。var schema2 = new Schema({ test: { type: String, index: true, unique: true // 若是指定`unique`为true,则为惟一索引 } });
lowercase
: 布尔值,是否在保存前对此值调用toLowerCase()
uppercase
: 布尔值,是否在保存前对此值调用toUpperCase()
trim
: 布尔值,是否在保存前对此值调用trim()
match
: 正则,建立一个验证器,验证值是否匹配给定的正则表达式enum
: 数组,建立一个验证器,验证值是不是给定数组中的元素min
: 数字,建立一个验证器,验证值是否大于等于给定的最小值max
: 数字,建立一个验证器,验证值是否小于等于给定的最大的值min
: Datemax
: Date如今已经介绍完Schematype
,接下来让咱们看一下Connections
。
咱们能够经过利用mongoose.connect()
方法链接 MongoDB 。
mongoose.connect('mongodb://localhost:27017/myapp');
这是链接运行在本地myapp
数据库最小的值(27017
)。若是链接失败,尝试用127.0.0.1
代替localhost
。
固然,你可在 uri 中指定更多的参数:
mongoose.connect('mongodb://username:password@host:port/database?options...');
意思就是咱们没必要等待链接创建成功就可使用 models,mongoose 会先缓存 model 操做
let TestModel = mongoose.model('Test', new Schema({ name: String })); // 链接成功前操做会被挂起 TestModel.findOne(function(error, result) { /* ... */ }); setTimeout(function() { mongoose.connect('mongodb://localhost/myapp'); }, 60000);
若是要禁用缓存,可修改bufferCommands
配置,也能够全局禁用 bufferCommands
mongoose.set('bufferCommands', false);
connect 方法也接收一个 options 对象:
mongoose.connect(uri, options);
这里我列举几个在平常使用中比较重要的选项,完整的链接选项看这里
bufferCommands
:这是 mongoose 中一个特殊的选项(不传递给 MongoDB 驱动),它能够禁用 mongoose 的缓冲机制
。user/pass
:身份验证的用户名和密码。这是 mongoose 中特殊的选项,它们能够等同于 MongoDB 驱动中的auth.user
和auth.password
选项。dbName
:指定链接哪一个数据库,并覆盖链接字符串中任意的数据库。useNewUrlParser
:底层 MongoDB 已经废弃当前链接字符串解析器。由于这是一个重大的改变,添加了 useNewUrlParser 标记若是在用户遇到 bug 时,容许用户在新的解析器中返回旧的解析器。poolSize
:MongoDB 驱动将为这个链接保持的最大 socket 数量。默认状况下,poolSize 是 5。useUnifiedTopology
:默认状况下为false
。设置为 true 表示选择使用 MongoDB 驱动程序的新链接管理引擎。您应该将此选项设置为 true,除非极少数状况会阻止您保持稳定的链接。示例:
const options = { useNewUrlParser: true, useUnifiedTopology: true, autoIndex: false, // 不建立索引 reconnectTries: Number.MAX_VALUE, // 老是尝试从新链接 reconnectInterval: 500, // 每500ms从新链接一次 poolSize: 10, // 维护最多10个socket链接 // 若是没有链接当即返回错误,而不是等待从新链接 bufferMaxEntries: 0, connectTimeoutMS: 10000, // 10s后放弃从新链接 socketTimeoutMS: 45000, // 在45s不活跃后关闭sockets family: 4 // 用IPv4, 跳过IPv6 }; mongoose.connect(uri, options);
connect()
函数也接收一个回调参数,其返回一个 promise。
mongoose.connect(uri, options, function(error) { // 检查错误,初始化链接。回调没有第二个参数。 }); // 或者用promise mongoose.connect(uri, options).then( () => { /** ready to use. The `mongoose.connect()` promise resolves to undefined. */ }, err => { /** handle initial connection error */ } );
说完Connections
,下面让咱们来看一个重点Models
Models
是从 Schema
编译来的构造函数。 它们的实例就表明着能够从数据库保存和读取的 documents
。 从数据库建立和读取 document 的全部操做都是经过 model
进行的。
const mongoose = require("mongoose"); const { Schema, model } = mongoose; const answerSchema = new Schema( { __v: { type: Number, select: false }, content: { type: String, required: true }, }, { timestamps: true } ); module.exports = model("Answer", answerSchema);
定义好 model 以后,就能够进行一些增删改查操做了
若是是Entity
,使用save
方法;若是是Model
,使用create
方法或insertMany
方法。
// save([options], [options.safe], [options.validateBeforeSave], [fn]) let Person = mongoose.model("User", userSchema); let person1 = new Person({ name: '森林' }); person1.save() // 使用save()方法,须要先实例化为文档,再使用save()方法保存文档。而create()方法,则直接在模型Model上操做,而且能够同时新增多个文档 // Model.create(doc(s), [callback]) Person.create({ name: '森林' }, callback) // Model.insertMany(doc(s), [options], [callback]) Person.insertMany([{ name: '森林' }, { name: '之晨' }], function(err, docs) { })
说到这里,咱们先要补充说明一下 mongoose 里面的三个概念:schema
、model
和entity
:
schema
: 一种以文件形式存储的数据库模型骨架,不具有数据库的操做能力model
: 由 schema 发布生成的模型,具备抽象属性和行为的数据库操做对entity
: 由 Model 建立的实体,他的操做也会影响数据库
Schema、Model、Entity 的关系请牢记:
Schema生成Model,Model创造Entity
,Model 和 Entity 均可对数据库操做形成影响,但 Model 比 Entity 更具操做性。
对于 Mongoosecha 的查找文档很容易,它支持丰富的查询 MongoDB 语法。包括find
、findById
、findOne
等。
find()
第一个参数表示查询条件,第二个参数用于控制返回的字段,第三个参数用于配置查询参数,第四个参数是回调函数,回调函数的形式为function(err,docs){}
Model.find(conditions, [projection], [options], [callback])
下面让咱们依次看下 find()的各个参数在实际场景中的应用:
conditions
Model.find({})
Model.find({name:'森林'})
对比相关操做符
Model.find({ age: { $in: [18, 24]} })
返回 age
字段等于 18
或者 24
的全部 document。
逻辑相关操做符
// 返回 age 字段大于 24 或者 age 字段不存在的文档 Model.find( { age: { $not: { $lte: 24 }}})
字段相关操做符
数组字段的查找
// 使用 $all 查找同时存在 18 和 20 的 document Model.find({ age: { $all: [ 18, 20 ] } });
projection
指定要包含或排除哪些 document
字段(也称为查询“投影
”),必须同时指定包含或同时指定排除,不能混合指定,_id
除外。
在 mongoose 中有两种指定方式,字符串指定
和对象形式指定
。
字符串指定时在排除的字段前加 - 号,只写字段名的是包含。
Model.find({},'age'); Model.find({},'-name');
对象形式指定时,1
是包含,0
是排除。
Model.find({}, { age: 1 }); Model.find({}, { name: 0 });
options
// 三种方式实现 Model.find(filter,null,options) Model.find(filter).setOptions(options) Model.find(filter).<option>(xxx)
options 选项见官方文档 Query.prototype.setOptions()。
这里咱们只列举经常使用的:
sort
: 按照排序规则根据所给的字段进行排序,值能够是 asc, desc, ascending, descending, 1, 和 -1。limit
: 指定返回结果的最大数量skip
: 指定要跳过的文档数量lean
: 返回普通的 js 对象,而不是 Mongoose Documents
。建议不须要 mongoose 特殊处理就返给前端的数据都最好使用该方法转成普通 js 对象。// sort 两种方式指定排序 Model.find().sort('age -name'); // 字符串有 - 表明 descending 降序 Model.find().sort({age:'asc', name:-1});
sort
和 limit
同时使用时,调用的顺序并不重要,返回的数据都是先排序后限制数量。
// 效果同样 Model.find().limit(2).sort('age'); Model.find().sort('age').limit(2);
callback
Mongoose 中全部传入 callback
的查询,其格式都是 callback(error, result)
这种形式。若是出错,则 error 是出错信息,result 是 null;若是查询成功,则 error 是 null, result 是查询结果,查询结果的结构形式是根据查询方法的不一样而有不一样形式的。
find()
方法的查询结果是数组,即便没查询到内容,也会返回 [] 空数组。
findById
Model.findById(id,[projection],[options],[callback])
Model.findById(id)
至关于 Model.findOne({ _id: id })
。
看一下官方对于findOne
与findById
的对比:
不一样之处在于处理 id 为undefined
时的状况。findOne({ _id: undefined })
至关于findOne({})
,返回任意一条数据。而findById(undefined)
至关于findOne({ _id: null })
,返回null
。
查询结果:
{}
对象形式。undefined
或 null
,result 返回 null
。null
。findOne
该方法返回查找到的全部实例的第一个
Model.findOne(conditions, [projection], [options], [callback])
若是查询条件是 _id
,建议使用 findById()
。
查询结果:
{}
对象形式。每一个模型都有本身的更新方法,用于修改数据库中的文档,不将它们返回到您的应用程序。经常使用的有findOneAndUpdate()
、findByIdAndUpdate()
、update()
、updateMany()
等。
findOneAndUpdate()
Model.findOneAndUpdate(filter, update, [options], [callback])
filter
查询语句,和find()
同样。
filter 为{}
,则只更新第一条数据。
update
{operator: { field: value, ... }, ... }
必须使用 update 操做符。若是没有操做符或操做符不是update
操做符,统一被视为$set
操做(mongoose 特有)
字段相关操做符
符号 | 描述 |
---|---|
$set | 设置字段值 |
$currentDate | 设置字段值为当前时间,能够是 Date 或时间戳格式。 |
$min | 只有当指定值小于当前字段值时更新 |
$max | 只有当指定值大于当前字段值时更新 |
$inc | 将字段值增长指定数量 ,指定数量 能够是负数,表明减小。 |
$mul | 将字段值乘以指定数量 |
$unset | 删除指定字段,数组中的值删后改成 null。 |
数组字段相关操做符
符号 | 描述 |
---|---|
$ | 充当占位符,用来表示匹配查询条件的数组字段中的第一个元素 {operator:{ "arrayField.$" : value }} |
$addToSet | 向数组字段中添加以前不存在的元素 { $addToSet: {arrayField: value, ... }} ,value 是数组时可与 $each 组合使用。 |
$push | 向数组字段的末尾添加元素 { $push: { arrayField: value, ... } } ,value 是数组时可与 $each 等修饰符组合使用 |
$pop | 移除数组字段中的第一个或最后一个元素 { $pop: {arrayField: -1(first) / 1(last), ... } } |
$pull | 移除数组字段中与查询条件匹配的全部元素 { $pull: {arrayField: value / condition, ... } } |
$pullAll | 从数组中删除全部匹配的值 { $pullAll: { arrayField: [value1, value2 ... ], ... } } |
修饰符
符号 | 描述 |
---|---|
$each | 修饰 $push 和 $addToSet 操做符,以便为数组字段添加多个元素。 |
$position | 修饰 $push 操做符以指定要添加的元素在数组中的位置。 |
$slice | 修饰 $push 操做符以限制更新后的数组的大小。 |
$sort | 修饰 $push 操做符来从新排序数组字段中的元素。 |
修饰符执行的顺序(与定义的顺序无关):
options
Mongoose Documents
。true
返回更新后的数据,false
(默认)返回更新前的数据。false
。true
,则在更新以前删除值为 undefined
的属性。true
,则返回来自 MongoDB 的原生结果。callback
null
{}
形式)options
的 {new:true}
,更新成功返回更新后的该条数据( {}
形式)filter
为空,则更新第一条数据findByIdAndUpdate()
Model.findByIdAndUpdate(id, update, options, callback)
Model.findByIdAndUpdate(id, update)
至关于 Model.findOneAndUpdate({ _id: id }, update)
。
result 查询结果:
{}
对象形式。undefined
或 null
,result 返回 null
。null
。update()
Model.update(filter, update, options, callback)
options
false
,只更新第一条数据;为 true
时,符合查询条件的多条文档都会更新。false
,即 update
参数若是没有操做符或操做符不是 update 操做符,将会默认添加 $set
;若是为 true
,则不添加 $set
,视为覆盖原有文档。updateMany()
Model.updateMany(filter, update, options, callback)
更新符合查询条件的全部文档,至关于 Model.update(filter, update, { multi: true }, callback)
删除经常使用的有findOneAndDelete()
、findByIdAndDelete()
、deleteMany()
、findByIdAndRemove()
等。
findOneAndDelete()
Model.findOneAndDelete(filter, options, callback)
filter
find()
同样options
true
,则返回来自 MongoDB
的原生结果。callback
filter
的数据时,返回 null
。filter
为空或 {}
时,删除第一条数据。{}
形式的原数据。findByIdAndDelete()
Model.findByIdAndDelete(id, options, callback)
Model.findByIdAndDelete(id)
至关于 Model.findOneAndDelete({ _id: id })
。
callback
id
的数据时,返回 null
。id
为空或 undefined
时,返回 null
。{}
形式的原数据。deleteMany()
Model.deleteMany(filter, options, callback)
filter
filter
条件的文档。deleteOne()
Model.deleteOne(filter, options, callback)
filter
filter
条件的第一条文档。findOneAndRemove()
Model.findOneAndRemove(filter, options, callback)
用法与 findOneAndDelete()
同样,一个小小的区别是 findOneAndRemove()
会调用 MongoDB 原生的 findAndModify()
命令,而不是 findOneAndDelete()
命令。
建议使用 findOneAndDelete()
方法。
findByIdAndRemove()
Model.findByIdAndRemove(id, options, callback)
Model.findByIdAndRemove(id)
至关于 Model.findOneAndRemove({ _id: id })
。
remove()
Model.remove(filter, options, callback)
从集合中删除全部匹配 filter
条件的文档。要删除第一个匹配条件的文档,可将 single
选项设置为 true
。
看完Models
,最后让咱们来看下在实战中比较有用的Populate
Mongoose 的 populate()
能够连表查询
,即在另外的集合中引用其文档。
Populate()
能够自动替换 document
中的指定字段,替换内容从其余 collection
中获取。
建立 Model
的时候,可给该 Model
中关联存储其它集合 _id
的字段设置 ref
选项。ref
选项告诉 Mongoose
在使用 populate()
填充的时候使用哪一个 Model
。
const mongoose = require("mongoose"); const { Schema, model } = mongoose; const answerSchema = new Schema( { __v: { type: Number, select: false }, content: { type: String, required: true }, answerer: { type: Schema.Types.ObjectId, ref: "User", required: true, select: false }, questionId: { type: String, required: true }, voteCount: { type: Number, required: true, default: 0 } }, { timestamps: true } ); module.exports = model("Answer", answerSchema);
上例中 Answer
model 的 answerer
字段设为 ObjectId 数组。 ref 选项告诉 Mongoose 在填充的时候使用 User
model。全部储存在 answerer
中的 _id
都必须是 User
model 中 document
的 _id
。
ObjectId
、Number
、String
以及 Buffer
均可以做为 refs
使用。 可是最好仍是使用 ObjectId
。
在建立文档时,保存 refs
字段与保存普通属性同样,把 _id
的值赋给它就行了。
const Answer = require("../models/answers"); async create(ctx) { ctx.verifyParams({ content: { type: "string", required: true } }); const answerer = ctx.state.user._id; const { questionId } = ctx.params; const answer = await new Answer({ ...ctx.request.body, answerer, questionId }).save(); ctx.body = answer; }
填充document
const Answer = require("../models/answers"); const answer = await Answer.findById(ctx.params.id) .select(selectFields) .populate("answerer");
被填充的 answerer
字段已经不是原来的 _id
,而是被指定的 document
代替。这个 document
由另外一条 query
从数据库返回。
返回字段选择
若是只须要填充 document
中一部分字段,可给 populate()
传入第二个参数,参数形式即 返回字段字符串
,同 Query.prototype.select()。
const answer = await Answer.findById(ctx.params.id) .select(selectFields) .populate("answerer", "name -_id");
populate 多个字段
const populateStr = fields && fields .split(";") .filter(f => f) .map(f => { if (f === "employments") { return "employments.company employments.job"; } if (f === "educations") { return "educations.school educations.major"; } return f; }) .join(" "); const user = await User.findById(ctx.params.id) .select(selectFields) .populate(populateStr);
到这里本篇文章也就结束了,这里主要是结合我平时的项目(https://github.com/Jack-cool/rest_node_api
)中对于mongoose
的使用作的简单的总结。但愿能给你带来帮助!
同时你能够关注个人同名公众号【前端森林】,这里我会按期发一些大前端相关的前沿文章和平常开发过程当中的实战总结。