繼上篇文章「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
的使用作的簡單的總結。但願能給你帶來幫助!
同時你能夠關注個人同名公衆號【前端森林】,這裏我會按期發一些大前端相關的前沿文章和平常開發過程當中的實戰總結。