## MongoDB 和 Mongoose
創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),伊春企業(yè)網(wǎng)站建設(shè),伊春品牌網(wǎng)站建設(shè),網(wǎng)站定制,伊春網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,伊春網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競爭力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。
### mongoose
建立一個(gè) MongoDB Atlas 數(shù)據(jù)庫并導(dǎo)入連接到它所需的軟件包。將 `mongodb@~3.6.0` 和 `mongoose@~5.4.0` 添加到項(xiàng)目的 `package.json` 中。 然后,在 `myApp.js` 文件中請求 `mongoose`。 創(chuàng)建一個(gè) `.env` 文件,給它添加一個(gè) `MONGO_URI` 變量。 變量的值為 MongoDB Atlas 數(shù)據(jù)庫 URI。 應(yīng)用單引號或雙引號包裹 URI。請記住,環(huán)境變量 `=` 兩邊不能有空格。 例如應(yīng)該這樣寫:`MONGO_URI='VALUE'`。 完成后,使用下面的代碼來連接數(shù)據(jù)庫。
```js
mongoose.connect(
```
```js
const mongoose = require('mongoose');
mongoose.connect("mongodb+srv://
```
### 創(chuàng)建模型 Model
**C** RUD 第一小節(jié)——CREATE
首先,需要一個(gè) Schema, 每一個(gè) Schema 都對應(yīng)一個(gè) MongoDB 的 collection, 并且在相應(yīng)的 collection 里定義 documents 的“樣子”。 Schema 用于組成模型(Model), 可以通過嵌套 Schema 來創(chuàng)建復(fù)雜的模型??梢愿鶕?jù)模型創(chuàng)建實(shí)例,模型實(shí)例化后的對象稱為 documents。
handler 函數(shù)會(huì)在特定事件(比如調(diào)用服務(wù)器 API)發(fā)生時(shí)執(zhí)行。 `done()` 是一個(gè)回調(diào)函數(shù),它的作用是在一個(gè)異步操作(比如對數(shù)據(jù)庫進(jìn)行插入、查詢、更新或刪除)執(zhí)行完成時(shí),告知可以繼續(xù)執(zhí)行后續(xù)的其它代碼。 這與 Node.js 中的處理方式十分類似,在 Node.js 中,在(異步操作)成功時(shí)調(diào)用 `done(null, data)`,在失敗時(shí)調(diào)用 `done(err)`。
注意:與遠(yuǎn)程服務(wù)器進(jìn)行交互時(shí),需要考慮到發(fā)生錯(cuò)誤的可能!
```js
/* Example */
const someFunc = function(done) {
//... do something (risky) ...
if (error) return done(error);
done(null, result);
};
```
按下面的原型信息創(chuàng)建一個(gè)名為 `personSchema` 的 schema:
```markup
- Person Prototype -
--------------------
name : string [required]
age : number
favoriteFoods : array of strings (*)
```
采用 Mongoose 基礎(chǔ) schema 類型。 如果還想添加更多的鍵,就請使用 required 或 unique 等簡單的驗(yàn)證器(validators),并設(shè)置默認(rèn)值。 詳情請參考 [Mongoose 文檔](http://mongoosejs.com/docs/guide.html)。
請從 `personSchema` 創(chuàng)建一個(gè)名為 `Person` 的 model。
```js
const Schema = mongoose.Schema;
const personSchema = new Schema({
name: { type: String, required: true },
age: Number,
favoriteFoods: [String]
});
const Person = mongoose.model("Person", personSchema);
```
### 創(chuàng)建并保存一條 Model 記錄
在 `createAndSavePerson` 函數(shù)中,使用在上一個(gè)挑戰(zhàn)中寫好的 `Person` 構(gòu)造函數(shù)創(chuàng)建 document 實(shí)例, 將包含 `name`、`age` 和 `favoriteFoods` 的對象傳給構(gòu)造函數(shù), 這些屬性的數(shù)據(jù)類型必須符合在 `personSchema` 中定義的類型。 然后在返回的 document 實(shí)例上調(diào)用方法 `document.save()`。 同時(shí),按 Node.js 的方式為它傳一個(gè)回調(diào)函數(shù)。 這是一種常見模式,以下所有 CRUD 方法都將這樣的回調(diào)函數(shù)作為最后一個(gè)參數(shù)。
```js
/* Example */
// ...
person.save(function(err, data) {
// ...do your stuff here...
});
```
```js
let createAndSavePerson = function(done) {
let janeFonda = new Person({name: "Jane Fonda", age: 84, favoriteFoods: ["eggs", "fish", "fresh fruit"]});
janeFonda.save(function(err, data) {
if (err) return console.error(err);
done(null, data)
});
};
```
### model.create() 創(chuàng)建多條記錄
在一些情況下,比如進(jìn)行數(shù)據(jù)庫初始化,會(huì)需要?jiǎng)?chuàng)建很多 model 實(shí)例來用作初始數(shù)據(jù)。 `Model.create()` 接受一組像 `[{name: 'John', ...}, {...}, ...]` 的數(shù)組作為第一個(gè)參數(shù),并將其保存到數(shù)據(jù)庫。
```js
let arrayOfPeople = [
{name: "Frankie", age: 74, favoriteFoods: ["Del Taco"]},
{name: "Sol", age: 76, favoriteFoods: ["roast chicken"]},
{name: "Robert", age: 78, favoriteFoods: ["wine"]}
];
let createManyPeople = function(arrayOfPeople, done) {
Person.create(arrayOfPeople, function (err, people) {
if (err) return console.log(err);
done(null, people);
});
};
```
### model.find() 查詢數(shù)據(jù)庫
C **R** UD 第二小節(jié)—— Read 查詢
嘗試一種最簡單的用法,`Model.find()` 接收一個(gè)查詢 document(一個(gè) JSON 對象)作為第一個(gè)參數(shù),一個(gè)回調(diào)函數(shù)作為第二個(gè)參數(shù), 它會(huì)返回由匹配到的數(shù)據(jù)組成的數(shù)組。 這個(gè)方法支持很多搜索選項(xiàng), 詳情請參閱文檔。
```js
let findPeopleByName = function(personName, done) {
Person.find({name: personName}, function (err, personFound) {
if (err) return console.log(err);
done(null, personFound);
});
};
```
### model.findOne() 返回一個(gè)單一匹配
`Model.findOne()` 與 `Model.find()` 十分類似,但就算數(shù)據(jù)庫中有很多條數(shù)據(jù)可以匹配查詢條件,它也只返回一個(gè) document,而不會(huì)返回一個(gè)數(shù)組, 如果查詢條件是聲明為唯一值的屬性,它會(huì)更加適用。
```js
let findOneByFood = function(food, done) {
Person.findOne({favoriteFoods: food}, function (err, data) {
if (err) return console.log(err);
done(null, data);
});
};
```
### model.findById() 根據(jù) _id 搜索
在保存 document 的時(shí)候,MongoDB 會(huì)自動(dòng)為它添加 `_id` 字段,并給該字段設(shè)置一個(gè)唯一的僅包含數(shù)字和字母的值。 通過 `_id` 搜索是一個(gè)十分常見的操作,為此,Mongoose 提供了一個(gè)專門的方法。
```js
var findPersonById = function(personId, done) {
Person.findById(personId, function (err, data) {
if (err) return console.log(err);
done(null, data);
});
};
```
### 通過執(zhí)行查詢、編輯、保存來執(zhí)行經(jīng)典更新流程
CR **U** D 第三小節(jié)—— Update 更新
在過去,如果想要編輯 document 并以某種方式使用它(比如放到服務(wù)器的返回?cái)?shù)據(jù)中),就必須執(zhí)行查找、編輯和保存。 Mongoose 有一個(gè)專用的更新方法 `Model.update()`, 它與底層的 mongo 驅(qū)動(dòng)綁定。 通過這個(gè)方法,可以批量編輯符合特定條件的多個(gè) document。但問題在于,這個(gè)方法不會(huì)返回更新后的 document,而是返回狀態(tài)信息。 此外,它直接調(diào)用 mongo 的底層驅(qū)動(dòng),讓處理 model 的驗(yàn)證變得更加棘手。
```js
const findEditThenSave = (personId, done) => {
const foodToAdd = 'hamburger';
Person.findById(personId, (err, person) => {
if(err) return console.log(err); person.favoriteFoods.push(foodToAdd);
person.save((err, updatedPerson) => {
if(err) return console.log(err);
done(null, updatedPerson)
})
})
};
```
### 在 document 中執(zhí)行新的更新方式——使用 model.findOneAndUpdate()
最近發(fā)布的 mongoose 版本簡化了 document 的更新方式, 但同時(shí),一些高級功能(如 pre/post hook, 驗(yàn)證)的使用方式也變得和以前不同。因此,在很多情景下,上一個(gè)挑戰(zhàn)中提到的老方法其實(shí)更常用。 新方法的加入,可以讓我們使用 `findByIdAndUpdate()` 來進(jìn)行基于 id 的搜索。
```js
const findAndUpdate = (personName, done) => {
const ageToSet = 20;
Person.findOneAndUpdate({name: personName}, {age: ageToSet}, {new: true}, (err, updatedDoc) => {
if(err) return console.log(err);
done(null, updatedDoc);
})
};
```
**提示:** 需要返回更新后的 document。 可以把 `findOneAndUpdate()` 的第三個(gè)參數(shù)設(shè)置為 `{ new: true }` 。 默認(rèn)情況下,這個(gè)方法會(huì)返回修改前的數(shù)據(jù)。
### model.findByIdAndRemove 刪除一個(gè) document
CRU **D** 第四小節(jié)—— Delete 刪除
`findByIdAndRemove` 和 `findOneAndRemove` 類似于之前的更新方法, 它們將被刪除的 document 傳給數(shù)據(jù)庫。 和之前一樣,使用函數(shù)參數(shù) `personId` 作為查詢關(guān)鍵字。
修改 `removeById` 函數(shù),通過 `_id` 刪除一個(gè)人的數(shù)據(jù), 可以使用 `findByIdAndRemove()` 或 `findOneAndRemove()` 方法。
```js
let removeById = function(personId, done) {
Person.findByIdAndRemove(
personId,
(err, removedDoc) => {
if(err) return console.log(err);
done(null, removedDoc);
}
);
};
```
### model.remove() 刪除多個(gè) document
`Model.remove()` 可以用于刪除符合給定匹配條件的所有 document。
修改 `removeManyPeople` 函數(shù),使用 `nameToRemove` 刪除所有姓名是變量 `Model.remove()` 的人。 給它傳入一個(gè)帶有 `name` 字段的查詢 document 和一個(gè)回調(diào)函數(shù)。
**注意:** `Model.remove()` 不會(huì)返回被刪除的 document,而是會(huì)返回一個(gè)包含操作結(jié)果以及受影響的數(shù)據(jù)數(shù)量的 JSON 對象。 不要忘記將它傳入 `done()` 回調(diào)函數(shù),因?yàn)槲覀冃枰谔魬?zhàn)的測試中調(diào)用它。
```js
const removeManyPeople = (done) => {
const nameToRemove = "Mary";
Person.remove({name: nameToRemove}, (err, response) => {
if(err) return console.log(err);
done(null, response);
})
};
```
### 鏈?zhǔn)秸{(diào)用輔助查詢函數(shù)來縮小搜索結(jié)果
如果不給 `Model.find()`(或者別的搜索方法)的最后一個(gè)參數(shù)傳入回調(diào)函數(shù), 查詢將不會(huì)執(zhí)行。 可以將查詢條件存儲(chǔ)在變量中供以后使用, 也可以通過鏈?zhǔn)秸{(diào)用這類變量來構(gòu)建新的查詢字段。 實(shí)際的數(shù)據(jù)庫操作會(huì)在最后調(diào)用 `.exec()` 方法時(shí)執(zhí)行。 必須把回調(diào)函數(shù)傳給最后一個(gè)方法。 Mongoose 提供了許多輔助查詢函數(shù), 這里使用最常見的一種。
修改 `queryChain` 函數(shù)來查詢喜歡 `foodToSearch` 食物的人。 同時(shí),需要將查詢結(jié)果按 `name` 屬性排序, 查詢結(jié)果應(yīng)限制在兩個(gè) document 內(nèi),并隱藏 age 屬性。 請鏈?zhǔn)秸{(diào)用 `.find()`、`.sort()`、`.limit()` 和 `.select()`,并在最后調(diào)用 `.exec()`, 并將 `done(err, data)` 回調(diào)函數(shù)傳入 `exec()`。
```js
let queryChain = function(done) {
var foodToSearch = "burrito";
var jsonObject = {favoriteFoods : foodToSearch};
Person.find(jsonObject).sort({name: 1}).limit(2).select({age: 0}).exec((err, data) => {
(err) ? done(err) : done(null, data);
})
};
```