mongoose Api CRUD操作返回結(jié)果總結(jié)梳理

大體概念

mongoose 中的Model是 由 Schema 編譯來的構(gòu)造函數(shù)剔桨,類似于JavaScript中的類腻暮,負(fù)責(zé)從底層MongoDB數(shù)據(jù)庫創(chuàng)建和讀取文檔(CRUD操作)彤守;Document就是Model的一個(gè)實(shí)例,SchemaModel定型哭靖,指定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ù)器了解即可

中間件 (也叫 prepost 鉤子) 是在異步函數(shù)執(zhí)行時(shí)傳入的控制函數(shù)粘拾。Mongoose 有四種中間件: document 中間件,model 中間件谅阿,aggregate 中間件半哟,和 query 中間件酬滤。

所有的中間件都支持 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)
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鸟顺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子器虾,更是在濱河造成了極大的恐慌讯嫂,老刑警劉巖养筒,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異端姚,居然都是意外死亡晕粪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門渐裸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巫湘,“玉大人,你說我怎么就攤上這事昏鹃∩蟹眨” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵洞渤,是天一觀的道長阅嘶。 經(jīng)常有香客問我,道長载迄,這世上最難降的妖魔是什么讯柔? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮护昧,結(jié)果婚禮上魂迄,老公的妹妹穿的比我還像新娘。我一直安慰自己惋耙,他們只是感情好捣炬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绽榛,像睡著了一般湿酸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上灭美,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天推溃,我揣著相機(jī)與錄音,去河邊找鬼冲粤。 笑死美莫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的梯捕。 我是一名探鬼主播厢呵,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼傀顾!你這毒婦竟也來了襟铭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寒砖,沒想到半個(gè)月后赐劣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哩都,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年魁兼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漠嵌。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咐汞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出儒鹿,到底是詐尸還是另有隱情化撕,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布约炎,位于F島的核電站植阴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏圾浅。R本人自食惡果不足惜掠手,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贱傀。 院中可真熱鬧惨撇,春花似錦、人聲如沸府寒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽株搔。三九已至,卻和暖如春纯蛾,著一層夾襖步出監(jiān)牢的瞬間纤房,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國打工翻诉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炮姨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓碰煌,卻偏偏與公主長得像舒岸,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芦圾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容