大體概念
mongoose 中的Model
是 由 Schema
編譯來的構(gòu)造函數(shù)剔桨,類似于JavaScript中的類腻暮,負(fù)責(zé)從底層MongoDB數(shù)據(jù)庫創(chuàng)建和讀取文檔(CRUD操作)彤守;Document
就是Model
的一個(gè)實(shí)例,Schema
給Model
定型哭靖,指定Model
包含的字段/
值的類型/
是否必傳等等規(guī)則遗增;而Query
則是定義好的命令,Model
的一些輔助函數(shù)返回的就是一個(gè)Query
對(duì)象款青。放在MySQL相當(dāng)于一條待執(zhí)行的sql語句做修,告訴數(shù)據(jù)庫這具體是一個(gè)什么操作。
增加(Create)抡草、檢索(Retrieve)饰及、更新(Update)和刪除(Delete)
我另一篇文 mongoose 5.x中的Promise和async/await
提到,每一個(gè)Mongoose Query康震,接收的回調(diào)都遵循 callback(error, result)
的模式燎含。
傳了回調(diào)即意味著執(zhí)行操作。如果執(zhí)行時(shí)發(fā)生錯(cuò)誤腿短,返回的error
即是錯(cuò)誤文檔屏箍, result 則會(huì)是 null
绘梦;若未出錯(cuò),error 為 null
赴魁,result 即是查詢的結(jié)果卸奉。如果我們不傳回調(diào),用 async/await
的 方式 去獲取返回颖御,那返回值的內(nèi)容等效于 result榄棵, error 就要靠我們用 catch 去捕獲了。
當(dāng)成功返回?cái)?shù)據(jù)(err 為 null
)潘拱,result
的格式取決于做的是什么操作:
新增文檔: Model.create() : ( Array|Object ) 成功創(chuàng)建的數(shù)據(jù)文檔 / 文檔數(shù)組
查詢單條文檔: Model.findOne()屋吨、Model.findById() :( result: Object ) 未查到符合條件的文檔則是
null
查詢文檔列表: Model.find() :( result: Array ) 符合
filter
條件的文檔數(shù)組镶蹋,沒有符合條件的則是空數(shù)組[]
查詢文檔數(shù)量: Model.countDocuments():( result: Number ) 沒有符合條件的則是
0
更新單個(gè)文檔/文檔列表:
(1) Model.update()、Model.updateMany()、Model.updateOne():( result: Object )
Object 參數(shù)說明:
ok
: Number (err 為 null 必然是1)
n
: Number (匹配filter條件的數(shù)量)
nModified: Number
: 被更新的文檔數(shù)量濒募。(如果沒查到符合條件的數(shù)據(jù)呵哨,則是0
刹前,也就是更新了0 條琳拨,不等于操作失敗)。
(2) Model.findOneAndUpdate()弛随、Model.findByIdAndUpdate():( result: Object ) 更新之前的文檔(默認(rèn))瓢喉,如果在options傳了new: true
,則是成功更新之后的文檔舀透。沒查到匹配的則是null
刪除單個(gè)/多個(gè)文檔:
(1) Model.deleteMany()栓票、Model.deleteOne():( result: Object )
Object 參數(shù)說明:
ok
: Number (err 為 null 必然是1)
deletedCount: Number
: 被刪除的文檔數(shù)量。(如果沒查到符合條件的數(shù)據(jù)愕够,則是0
走贪,也就是刪除了0 條,不等于操作失敗)
n: Number
: 已刪除文檔的數(shù)量惑芭。等于deletedCount坠狡。
(2) Model.findOneAndDelete()、Model.findByIdAndDelete():( result: Object ) 查詢到(被刪)的這條文檔遂跟,如果沒符合條件的則是null
Models API 文檔中有詳細(xì)描述被傳給回調(diào)函數(shù)的值逃沿。
我們做增刪改查操作都是通過Model方法,看過Middleware這一節(jié)就會(huì)明白幻锁,調(diào)用這些Api實(shí)際上是去觸發(fā)一些更深層的方法凯亮,比如
Document.save()
、Query.prototype.find()
哄尔。Model.create()
假消、Model.find()
等等都是被包裝過的快捷方式(語法糖)。(下面 中間件 和 Api返回值細(xì)則 部分會(huì)詳細(xì)講)
使用示例
同時(shí)我們也提到過因?yàn)镼uery返回的不是Promise岭接,通過加上exec()
便能返回完整的Promise富拗,所以我們現(xiàn)在一般結(jié)合await
獲取 result(推薦):
try {
const doc = await MyModel.findOne({ country: 'Croatia' }).exec()
// 如果沒有符合 { country: 'Croatia' } 的數(shù)據(jù)臼予,返回 `null`
if(doc === null) {
return res.json({
code: 400,
message: '沒有符合條件的數(shù)據(jù)'
})
}
res.json({
code: 200,
data: docs
message: '查詢成功'
})
} catch (err) {
console.log(err)
}
也可使用回調(diào)拿 result:
// 查找 name 含有 john 的數(shù)據(jù),并且返回的列表中的對(duì)象只需要 "name" 和 "friends" 這兩個(gè)字段
MyModel.find({ name: /john/i }, 'name friends').exec((err, docs) => {
if(err) return next(err)
if(docs.length === 0) {
return res.json({
code: 400,
message: '沒有符合條件的數(shù)據(jù)'
})
}
console.log(docs)
})
關(guān)于中間件(這一部分主要為了加深對(duì)mongoose操作數(shù)據(jù)Api的理解啃沪,如果只是作為前端搭建簡單的服務(wù)器了解即可
)
中間件 (也叫 pre
和 post
鉤子) 是在異步函數(shù)執(zhí)行時(shí)傳入的控制函數(shù)粘拾。Mongoose 有四種中間件: document 中間件,model 中間件谅阿,aggregate 中間件半哟,和 query 中間件酬滤。
Document 中間件支持這些document方法:validate签餐、save、remove盯串、updateOne氯檐、deleteOne、init (note: init鉤子 是同步 synchronous)
Query 中間件支持如下 Model 和 Query 的方法:count体捏、deleteMany冠摄、deleteOne、find几缭、findOne河泳、findOneAndDelete、findOneAndRemove年栓、findOneAndUpdate拆挥、remove、update某抓、updateOne纸兔、updateMany
Aggregate 中間件用于' MyModel.aggregate() ':aggregate。當(dāng)你在一個(gè)Aggregate對(duì)象上調(diào)用' exec() '時(shí)否副,Aggregate 中間件會(huì)執(zhí)行汉矿。在 aggregate 中間件,
this
指向aggregation對(duì)象Model 中間件支持以下model方法:insertMany. 在 model 中間件函數(shù)中,
this
指向當(dāng)前model。
所有的中間件都支持 pre 和 post 鉤子备禀,它們的工作方式是怎樣的呢:
錯(cuò)誤處理
如果 pre 鉤子出錯(cuò)洲拇,mongoose 將不會(huì)執(zhí)行后面的函數(shù)。 Mongoose 會(huì)向回調(diào)函數(shù)傳入 err 參數(shù)曲尸, 或者 reject 返回的 promise赋续。
schema.pre('save', function(next) {
const err = new Error('something went wrong');
// If you call `next()` with an argument, that argument is assumed to be
// an error.
next(err);
});
schema.pre('save', function() {
// You can also return a promise that rejects
return new Promise((resolve, reject) => {
reject(new Error('something went wrong'));
});
});
schema.pre('save', function() {
// You can also throw a synchronous error
throw new Error('something went wrong');
});
schema.pre('save', async function() {
await Promise.resolve();
// You can also throw an error in an `async` function
throw new Error('something went wrong');
});
// save做的更改不會(huì)執(zhí)行到 MongoDB,因?yàn)橐粋€(gè)pre鉤子(預(yù)處理鉤子)出錯(cuò)了
myDoc.save(function(err) {
console.log(err.message); // 出錯(cuò)了
});
Post 中間件
post 中間件在方法執(zhí)行之后調(diào)用队腐,這個(gè)時(shí)候每個(gè) pre
中間件都已經(jīng)完成蚕捉。
異步 Post 鉤子
如果你給回調(diào)函數(shù)傳入兩個(gè)參數(shù),mongoose 會(huì)認(rèn)為第二個(gè)參數(shù)是 next()
函數(shù)柴淘,你可以通過 next 觸發(fā)下一個(gè)中間件
// Takes 2 parameters: this is an asynchronous post hook
schema.post('save', function(doc, next) {
setTimeout(function() {
console.log('post1');
// Kick off the second post hook
next();
}, 10);
});
// Will not execute until the first middleware calls `next()`
schema.post('save', function(doc, next) {
console.log('post2');
next();
});
Save/Validate Hooks
save()
方法會(huì)觸發(fā)validate()
鉤子迫淹,因?yàn)閙ongoose有一個(gè)內(nèi)置的pre('save')
鉤子會(huì)調(diào)用validate()
秘通。這意味著所有的pre('validate')
和post('validate')
鉤子都會(huì)在任何pre('save')
鉤子之前被調(diào)用。
schema.pre('validate', function() {
console.log('this gets printed first');
});
schema.post('validate', function() {
console.log('this gets printed second');
});
schema.pre('save', function() {
console.log('this gets printed third');
});
schema.post('save', function() {
console.log('this gets printed fourth');
});
findAndUpdate() 與 Query 中間件注意事項(xiàng)
Pre 和 post save() 鉤子都不會(huì)在update()
, findOneAndUpdate()
等 Query 方法上執(zhí)行敛熬。
Query 中間件與 document 中間件有一個(gè)細(xì)微但重要的區(qū)別:在 document 中間件的this
指的是正在被更新的文檔肺稀。而query 中間件,mongoose不一定有一個(gè)對(duì)正在更新的文檔的引用应民,所以它的this
指向query對(duì)象话原。
利用這一點(diǎn),如果你想為每個(gè)updateOne()調(diào)用添加一個(gè)updatedAt時(shí)間戳诲锹,可以使用下面的pre鉤子繁仁。
schema.pre('updateOne', function() {
// this指 query 對(duì)象
this.set({ updatedAt: new Date() });
});
同樣的,你不能在pre('updateOne')
或pre('findOneAndUpdate')
query中間件中訪問要被更新的文檔归园。如果需要訪問這個(gè)文檔黄虱,則需要對(duì)該文檔執(zhí)行顯式查詢。如下:
schema.pre('findOneAndUpdate', async function() {
const docToUpdate = await this.model.findOne(this.getQuery());
console.log(docToUpdate); // The document that `findOneAndUpdate()` will modify
});
Error Handling Middleware
中間件的執(zhí)行通常在出現(xiàn)錯(cuò)誤并調(diào)用next(err)時(shí)停止庸诱。然而有一種特殊的post中間件稱為“錯(cuò)誤處理中間件”捻浦,它特定在錯(cuò)誤發(fā)生時(shí)執(zhí)行。錯(cuò)誤處理中間件對(duì)于報(bào)告錯(cuò)誤和提高錯(cuò)誤消息的可讀性非常有用桥爽。
錯(cuò)誤處理中間件比普通中間件多一個(gè) error 參數(shù)朱灿,并且這個(gè) error 作為第一個(gè)參數(shù)傳入。 然后錯(cuò)誤處理中間件可以讓你自由地進(jìn)行錯(cuò)誤的后續(xù)處理钠四。
const schema = new Schema({
name: {
type: String,
// Will trigger a MongoError with code 11000 when
// you save a duplicate
unique: true
}
});
// Handler **must** take 3 parameters: the error that occurred, the document
// in question, and the `next()` function
schema.post('save', function(error, doc, next) {
if (error.name === 'MongoError' && error.code === 11000) {
next(new Error('There was a duplicate key error'));
} else {
next();
}
});
// Will trigger the `post('save')` error handler
Person.create([{ name: 'Axl Rose' }, { name: 'Axl Rose' }]);
query 中間件也可以使用錯(cuò)誤處理中間件盗扒。你可以定義一個(gè) post update() 鉤子, 它可以捕獲 MongoDB 重復(fù) key 錯(cuò)誤形导。
// The same E11000 error can occur when you call `update()`
// This function **must** take 3 parameters. If you use the
// `passRawResult` function, this function **must** take 4
// parameters
schema.post('update', function(error, res, next) {
if (error.name === 'MongoError' && error.code === 11000) {
next(new Error('There was a duplicate key error'));
} else {
next(); // The `update()` call will still error out.
}
});
const people = [{ name: 'Axl Rose' }, { name: 'Slash' }];
Person.create(people, function(error) {
Person.update({ name: 'Slash' }, { $set: { name: 'Axl Rose' } }, function(error) {
// `error.message` will be "There was a duplicate key error"
});
});
錯(cuò)誤處理中間件可以轉(zhuǎn)換錯(cuò)誤环疼,但不能移除錯(cuò)誤。只要沒有調(diào)用next(error)
把錯(cuò)誤傳遞給后面的中間件去處理朵耕,這個(gè)中間件的調(diào)用仍然會(huì)出錯(cuò)炫隶。
————————————————————————————————————————
*主要看這里*
result 參數(shù)具體格式細(xì)則表【即 (err, result) 中的 result】
新增數(shù)據(jù)
實(shí)際觸發(fā)的中間件方法 Document.prototype.save():如果該document的
isNew
參數(shù)是true
,則會(huì)在數(shù)據(jù)庫中插入一個(gè)新文檔阎曹。否則伪阶,只會(huì)發(fā)送一個(gè)updateOne
操作對(duì)原數(shù)據(jù)進(jìn)行修改。
Model.create()
docs : Array|Object
(返回成功創(chuàng)建的數(shù)據(jù)文檔/文檔數(shù)組)
觸發(fā)的中間件: save()
create()
是向數(shù)據(jù)庫保存一條或多條文檔的快捷方式处嫌。MyModel.create(docs)
會(huì)對(duì)docs的每一條文檔執(zhí)行一次new MyModel(doc).save()
查詢數(shù)據(jù)
Mongoose支持 MongoDB豐富的查詢語法 栅贴。可以用
Model.
find, findById, findOne, or where 靜態(tài)方法來檢索文檔熏迹。
(1) 查詢符合條件的第一條數(shù)據(jù)
Query.prototype.findOne()
doc: Object
(沒有符合條件的是 null
)
基于它的方法:Model.findOne()檐薯、Model.findById() (Model
的一些方法返回Query
對(duì)象,實(shí)質(zhì)上調(diào)用的就是Query
的原型方法「作用是聲明一個(gè)操作命令」,result
一致故而可以參考坛缕,下文不再重復(fù)說明 )
(2) 查詢符合條件的全部文檔
Query.prototype.find()
docs: Array
(沒有符合條件的是 []
)
基于它的方法:Model.find()
(3) 查詢符合條件的文檔數(shù)量
Query.prototype.countDocuments()
count: Number
(沒有符合條件的是 0
)
基于它的方法:Model.countDocuments()
注??:count() 已被棄用墓猎,請(qǐng)用countDocuments()
替代
更新數(shù)據(jù)
更新數(shù)據(jù)有兩種方式,先檢索 findOne() 再保存 save() 通常是使用Mongoose更新文檔的正確方式赚楚。通過
save()
毙沾,我們可以得到完整的驗(yàn)證和中間件。
但當(dāng)我們僅僅需要更新而不需要獲取數(shù)據(jù)時(shí)宠页,一些Query方法就很適合我們左胞,比如 updateMany()。
update()
举户, updateMany()
烤宙, findOneAndUpdate()
等不會(huì)執(zhí)行save()
中間件,因此也不會(huì)在修改數(shù)據(jù)庫時(shí)先執(zhí)行任何鉤子或驗(yàn)證敛摘。如果需要完全成熟的驗(yàn)證门烂,可以使用先檢索文檔再save()
的傳統(tǒng)方法乳愉。
(1) 更新單條數(shù)據(jù)
Query.prototype.findOneAndUpdate()
doc: Object
默認(rèn)是更新之前的文檔對(duì)象兄淫,如果在options傳了new: true
,則返回成功更新之后的文檔對(duì)象蔓姚。沒查到匹配的則是null
發(fā)起的 mongodb
命令:findAndModify 更新指令捕虽。
基于它的方法:Model.findOneAndUpdate()、Model.findByIdAndUpdate()
Query.prototype.updateOne
writeOpResult: Object
內(nèi)含參數(shù)如下:
ok
: Number (err 為 null 必然是1)
n
: Number (匹配filter條件的數(shù)量)
nModified: Number
: 被更新的文檔數(shù)量坡脐。(如果沒查到符合條件的數(shù)據(jù)泄私,則是 0
,也就是更新了0 條备闲,不等于操作失敗)
基于它的方法:Model.updateOne()
{ n: 1, nModified: 1, ok: 1 }
只更新匹配filter的第一個(gè)文檔晌端。
(2) 更新多條數(shù)據(jù)
Query.prototype.update
writeOpResult: Object
對(duì)象內(nèi)容同 UpdateOne
基于它的方法:Model.update()
Query.prototype.updateMany
writeOpResult: Object
對(duì)象內(nèi)容同 UpdateOne
基于它的方法:Model.updateMany()
刪除數(shù)據(jù)
(1) 刪除單條數(shù)據(jù)
Query.prototype.deleteOne()
mongooseDeleteResutl: Object
,內(nèi)含三個(gè)參數(shù):
ok
: Number (err 為 null 必然是1)
deletedCount: Number
: 被刪除的文檔數(shù)量恬砂。(如果沒查到符合條件的數(shù)據(jù)咧纠,則是 0
,也就是刪除了0 條泻骤,不等于操作失敗)
n: Number
: 已刪除文檔的數(shù)量漆羔。等于deletedCount。
這個(gè)方法調(diào)用 MongoDB driver's Collection#deleteOne()
function.
基于它的方法:Model.deleteOne()
{ok: 1, deletedCount:1, n: 1}
Query.prototype.findOneAndDelete()
doc: Object
查詢到的(要被刪的)的這條數(shù)據(jù)狱掂,如果沒查到符合的則是 null
需要返回被刪文檔數(shù)據(jù)的時(shí)候使用演痒。
發(fā)起的 MongoDB
命令: findOneAndDelete 指令
基于它的方法:Model.findOneAndDelete()、Model.findByIdAndDelete()
(2) 刪除多條數(shù)據(jù)
Query.prototype.deleteMany()
mongooseDeleteResult: Object
對(duì)象內(nèi)容同 deleteOne
這個(gè)方法調(diào)用MongoDB driver的Collection#deleteMany()
function趋惨。
基于它的方法:Model.deleteMany()
示例:
const id = req.params.id.split(',')
try {
const response = await Model.deleteMany({ _id: { $in: id } }).exec()
if(response.ok && response.deletedCount) {
res.json({
code: 200,
message: '批量刪除成功'
})
} else{
res.json({
code: 400,
message: '刪除失敗'
})
}
} catch (err) {
next(err)
}