# 在Node中基于Mongoose對MongoDB進(jìn)行增刪查改(CRUD)操作

工具介紹

MongoDB

MongoDB是基于Javascript語言的數(shù)據(jù)庫腰涧,存儲格式是JSON,而Node也是基于JavaScript的環(huán)境(庫)紊浩,所以node和mongoDB的搭配能減少因為數(shù)據(jù)轉(zhuǎn)換帶來的時間空間開銷窖铡。

Mongoose

是MongoDB的一個對象模型工具疗锐,它將數(shù)據(jù)庫中的數(shù)據(jù)轉(zhuǎn)換為JavaScript對象以供你在應(yīng)用中使用,封裝了MongoDB對文檔的的一些增刪改查等常用方法费彼,讓NodeJS操作Mongodb數(shù)據(jù)庫變得更加靈活簡單滑臊。

Robomongo

一個可視化的mongoDB操作軟件,類似于mysql的navicat可視化工具箍铲。

捋一捋它們的關(guān)系简珠,mongoDB是一個數(shù)據(jù)庫,mongoose是你在自己代碼中操作mongo數(shù)據(jù)庫的接口虹钮,而robomongo是mongo數(shù)據(jù)庫的可視化工具聋庵,通過它的界面方便直接操作數(shù)據(jù)庫內(nèi)容。

工具安裝

MongoDB安裝

  1. 安裝mongoDB

到官網(wǎng)https://www.mongodb.com/download-center#community下載程序安裝芙粱,選擇custom模式就行祭玉。

  1. 建立MongoDB環(huán)境

需要自己建立db目錄作為數(shù)據(jù)庫環(huán)境,在命令行窗口中輸入

$ md \data\db
建立db文件夾后春畔,在命令窗口中進(jìn)入安裝目錄的bin文件夾執(zhí)行mongod.exe脱货,把數(shù)據(jù)庫安裝在datadb中。mongoDB會檢測你的根目錄是否有datadb文件夾律姨,如果有會默認(rèn)安裝到這個文件夾里面振峻。


$ mongod.exe

當(dāng)然也可以直接在系統(tǒng)根目錄下創(chuàng)建datadb文件夾,然后在mongoDB安裝文件夾中雙擊執(zhí)行mongod.exe。

  1. 啟動MongoDB

命令行工具中輸入:

$ cd C:\Program Files\MongoDB\Server\3.2\bin

 $ mongod.exe

為了避免每次都要輸入目錄迈勋,所以在系統(tǒng)變量里面配置一下path變量计雌,把";C:Program FilesMongoDBServer3.2bin"放到path后面(記得加;隔開)凤价,以后可以直接在命令行窗口輸入mongod.exe回車即可。

在瀏覽器中輸入網(wǎng)址:http://localhost:27017/ 拔创。如果服務(wù)啟動成功會看到以下一段話:It looks like you are trying to access MongoDB over HTTP on the native driver port.

  1. 連接MongoDB(這一步基本沒有用利诺,只有在命令行工具中使用mongo原生方法時需要,而在mongoose里面會有連接的代碼剩燥,Robomongo運行也會有連接)

命令行工具中輸入mongo.exe慢逾,回車。

如果出現(xiàn)這個警告:2016-07-16T14:49:02.827+0800 I CONTROL [main] Hotfix KB2731284 or later update is not installed, will zero-out data files那是因為Windows缺少一個補(bǔ)丁灭红,從這個鏈接下周補(bǔ)丁451413_intl_x64_zip侣滩,然后解壓安裝包,在你解壓的目錄下找到Windows6.1-KB2731284-v3-x64.mus安裝文件比伏。安裝重啟即可胜卤。

Mongoose安裝與加載

首先假定你已經(jīng)安裝了 Node.js,命令行工具輸入:

$ npm install mongoose -g
在使用的文件中require("mongoose");即可赁项。

使用Mongoose進(jìn)行CRUD操作

使用基本步驟

Mongose基于mongodb的原生方法葛躏,自己定義了一套操作MongoDB數(shù)據(jù)庫的接口澈段,比原生方法更加簡單方便。為了更加直觀舰攒,下面的步驟結(jié)合例子來講败富。假如我需要做一個教務(wù)系統(tǒng),需要存儲學(xué)生Student的信息摩窃,學(xué)生信息通常包含姓名name兽叮,學(xué)號id,電話phone猾愿,登錄日期date等鹦聪。我把學(xué)生的信息存在mongodb的myDB數(shù)據(jù)庫中,集合的名字叫students蒂秘。如圖:

這里寫圖片描述

_id這個域你可以自己定義泽本,但如果你沒有定義,系統(tǒng)會自動給你加上姻僧。下面先介紹在node中通過mongoose對mongodb進(jìn)行操作的必須前提步驟:

  1. node連接數(shù)據(jù)庫

mongoose.connect('mongodb://user:pass@ip:port/database');
這只是最基本的連接规丽,我們一般還會加一些設(shè)置,是否開啟調(diào)試模式撇贺,連接提示等赌莺。通常我會這么寫:

var mongoose = require("mongoose");
mongoose.Promise = global.Promise;

/*調(diào)試模式是mongoose提供的一個非常實用的功能,用于查看mongoose模塊對mongodb操作的日志松嘶,一般開發(fā)時會打開此功能艘狭,以便更好的了解和優(yōu)化對mongodb的操作。*/
mongoose.set('debug', true);

/*一般默認(rèn)沒有user和password*/
var db=mongoose.connect('mongodb://localhost/myDB');

db.connection.on("error", function (error) {  
  console.log("數(shù)據(jù)庫連接失敶 :" + error); 
}); 

db.connection.on("open", function () {  
  console.log("數(shù)據(jù)庫連接成功"); 
});
  1. 定義模式(Schema)

每個模式映射mongoDB的一個集合(注意映射這個詞缓升,下面會講為什么)鼓鲁,它定義(只是定義蕴轨,不是實現(xiàn))這個集合里面文檔的結(jié)構(gòu),就是定義這個文檔有什么字段骇吭,字段類型是什么橙弱,字段默認(rèn)值是什么等。除了定義結(jié)構(gòu)外燥狰,還定義文檔的實例方法棘脐,靜態(tài)模型方法,復(fù)合索引蛀缝,中間件等嗤练。詳情自己查看mongoose官方文檔革答。

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

/*定義模式Student_Schema*/
var Student_Schema = new Schema({
  name: String,
  id: Number,
  phone: String,
  date: Date
}, {
  versionKey: false
});

/*定義模型Student碟嘴,注意數(shù)據(jù)庫存的是students*/
mongoose.model("Student", Student_Schema);

{versionKey: false}是干嘛用眠菇?如果不加這個設(shè)置登疗,我們通過mongoose第一次創(chuàng)建某個集合時脱吱,它會給這個集合設(shè)定一個versionKey屬性值宦搬,這個屬性值包含這個文檔的內(nèi)部版本憔足,數(shù)據(jù)庫中顯示為_v,通過{versionKey: false}可以配置這個參數(shù),讓數(shù)據(jù)庫不再添加這個屬性,格式是:new Schema({..}, { versionKey: false });

  1. 定義模型(Model)

模型用來實現(xiàn)我們定義的模式弓叛,調(diào)用mongoose.model來編譯Schema得到Model彰居。

/*定義模型Student,數(shù)據(jù)庫存的是students*/
mongoose.model("Student", Student_Schema);

為什么上面我強(qiáng)調(diào)模式的映射撰筷,那是因為模式僅僅是和db中集合文檔的結(jié)構(gòu)相對應(yīng)(映射)陈惰,它并不直接在數(shù)據(jù)庫中操作這個結(jié)構(gòu),模型才是直接與數(shù)據(jù)庫打交道的存在毕籽,可以這么說:模式是定義結(jié)構(gòu)抬闯,模型是實現(xiàn)操作。當(dāng)我們使用mongoose.model("Student", Student_Schema)創(chuàng)建Student模型對數(shù)據(jù)進(jìn)行操作時关筒,數(shù)據(jù)庫會尋找一個名字叫students集合接受Student模型的操作溶握,特別需要注意的是:1.如果是增加(instance.save)操作時,數(shù)據(jù)庫中沒有這個集合蒸播,數(shù)據(jù)庫會自動創(chuàng)建這個集合存儲數(shù)據(jù)睡榆,這個集合產(chǎn)生規(guī)則為:把Model名字字母全部變小寫和在后面加復(fù)數(shù)s。2.如果是刪改查三個操作數(shù)據(jù)庫中沒有這個集合袍榆,那就是沒有胀屿,刪除空修改空返回空。

  1. 訪問模型

var MyStudent = mongoose.model("Student");
到這里包雀,已經(jīng)基本完成了使用mongoose前提操作了宿崭。有沒有覺得有點繁瑣,其實我也覺得挺繁瑣馏艾,幸運的是234可以一步創(chuàng)建:

var MyStudent = mongoose.model('Student',{
  name: String,
  id: Number,
  phone: String,
  date: Date
});
  1. 創(chuàng)建實例(instance)
var sam = new MyStudent({
    name: "sam976",
    id: 123,
    phone: "18706888888",
    date: Date.now()
});

一般只在save(增加)操作中需要劳曹。

模型的實例是集合中真實的數(shù)據(jù),就是collection中的document琅摩,用mysql中的術(shù)語來說就是一條記錄。模型在數(shù)據(jù)庫中建好了集合和文檔結(jié)構(gòu)后锭硼,通過實例往里面添加真實的document房资。

捋一捋模式、模型檀头、實例的關(guān)系:模式定義了操作和屬性轰异,這些操作和屬性包括mongoose自帶和自定義岖沛,而模型和實例可以對模式里面定義的屬性和方法進(jìn)行引用。模型是mongoose用來和數(shù)據(jù)庫直接打交道的中介搭独,實例是往數(shù)據(jù)庫存的真實數(shù)據(jù)婴削。模式并非必須,那為什么要分開模式和模型呢牙肝?我覺得是遵循了軟件設(shè)計中“定義和實現(xiàn)分開”這個原則唉俗。有的文章說模式?jīng)]有操作數(shù)據(jù)庫的能力,模型才有配椭,對這個觀點虫溜,我覺得部分對,雖說模式不能直接操作數(shù)據(jù)庫股缸,但模式定義的方法可以被模型用來操作數(shù)據(jù)庫衡楞。官方文檔是這么說的:

Schemas not only define the structure of your document and casting of properties, they also define document instance methods, static Model methods, compound indexes and document lifecycle hooks called middleware.

以上是使用mongoose進(jìn)行增刪查改操作都需要經(jīng)過的前提步驟,下面正式介紹對數(shù)據(jù)庫的增刪查改(CRUD)操作敦姻。

CRUD操作

  1. CRUD之create

使用模型創(chuàng)建sam實例瘾境,sam實例調(diào)用save方法把document存入數(shù)據(jù)庫的students集合中,代碼如下

    var MyStudent = mongoose.model("Student");
    var sam = new MyStudent({
        name: "sam976",
        id: 123,
        phone: "18706888888",
        date: Date.now()
    });
    sam.save(function(err) {});

通過robomongo查看數(shù)據(jù)庫镰惦,可以看到數(shù)據(jù)已經(jīng)存放成功寄雀,

  1. CRUD之read

使用MyStudent模型調(diào)用find()方法返回students集合的所有內(nèi)容,第一個參數(shù)定義條件陨献,第二個參數(shù)是回調(diào)函數(shù)盒犹,回調(diào)函數(shù)中的docs是返回的是查找結(jié)果,結(jié)果形式為一個json數(shù)據(jù)數(shù)組[{},{}]眨业。

    var MyStudent = mongoose.model("Student");
    MyStudent.find({}, function(err, docs) {});

模型還可以調(diào)用其他很多查詢的函數(shù)急膀,比如

Model.findById(id, [projection], [options], [callback]);
Model.findOne([conditions], [projection], [options], [callback])龄捡;
  1. CRUD之update

使用MyStudent模型調(diào)用update()方法完成更新卓嫂,第一個參數(shù)是條件(也就是where name="sam976"),第二個參數(shù)修改的內(nèi)容聘殖。

    var MyStudent = mongoose.model("Student");
    MyStudent.update({name:"sam976"},{id:456,phone:"12345678910"}, function(error){});

  1. CRUD之delete

使用MyStudent模型調(diào)用remove()方法刪除文檔晨雳。

var MyStudent = mongoose.model("Student");
MyStudent.remove({ name: 'sam976' }, function (err) {});

源碼結(jié)構(gòu)

使用mongoose的時候,通常會在項目中創(chuàng)建三個文件:connect.js奸腺,mongoose-db.js餐禁,app.js。

其中connect.js存放的是連接數(shù)據(jù)庫的操作突照,我們只需要加載一次即可在程序運行期間一直連接數(shù)據(jù)庫帮非。

mongoose-db.js文件存放模式和模型的生成的代碼,沒有連接信息,也沒有其他額外不相干代碼末盔,可以在在mongoose-db.js中把模型exports公開:

var MyStudent = mongoose.model("Student", Student_Schema);
exports.MyStudent=MyStudent;

/*定義其他模型和模式*/
var MyTeacher = mongoose.model("Teacher", Teacher_Schema);
exports.MyTeacher=MyTeacher;
然后在app.js中引用:

var MyStudent = require("./mongoose-db").MyStudent;
var MyTeacher = require("./mongoose-db").MyTeacher;

app.js存放對數(shù)據(jù)庫的操作筑舅,比如CRUD。通過這樣的方式陨舱,結(jié)構(gòu)比較清晰翠拣,代碼可讀性大大增強(qiáng)。

下面放源碼(目的是給自己備份游盲,笑臉...)

connect.js

var mongoose = require("mongoose");
mongoose.Promise = global.Promise;//為了解決過期的問題
/*調(diào)試模式是mongoose提供的一個非常實用的功能误墓,用于查看mongoose模塊對mongodb操作的日志,一般開發(fā)時會打開此功能背桐,以便更好的了解和優(yōu)化對mongodb的操作优烧。*/
mongoose.set('debug', true);
/*mongoose會緩存命令,只要connect成功链峭,處于其前其后的命令都會被執(zhí)行畦娄,connect命令也就無所謂放哪里*/
var db=mongoose.connect('mongodb://localhost/myDB');

db.connection.on("error", function (error) {  
  console.log("數(shù)據(jù)庫連接失敗:" + error); 
});

db.connection.on("open", function () {  
  console.log("數(shù)據(jù)庫連接成功"); 

mongoose-db.js

require('./connect');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
/*定義模式Student_Schema*/
var Student_Schema = new Schema({
  name: String,
  id: Number,
  phone: String,
  date: Date

}, {
  versionKey: false
});

/*定義模型Student弊仪,數(shù)據(jù)庫存的是students*/
var MyStudent = mongoose.model("Student", Student_Schema);
exports.MyStudent=MyStudent;

/*mongoose.Schema({
  username: {// 真實姓名
    type: String,
    required: true
  },
  password: { // 密碼
    type: String,
    required: true
  }
});*/

app.js

require("./mongoose-db");
var express = require("express");
var mongoose = require("mongoose");
var MyStudent = require("./mongoose-db").MyStudent;
var app = express();

app.use(express.static("./"));
app.get("/create", function(req, res) {
    console.log("create 函數(shù)")
    var beta = new MyStudent({
        name: "beta",
        id: 124,
        phone: "1871111111",
        date: Date.now()
    });
    beta.save(function(err) {
        if (err) {
            console.log(err);
        } else {
            console.log('存入成功');
        }
    });
    res.send("存入成功N蹩ā!");

});

app.get("/read", function(req, res) {
    console.log("讀取函數(shù)");
    MyStudent.find({}, function(err, docs) {
        console.log(docs);
        /*對docs進(jìn)行操作*/
    });

    res.send("讀取成功@驳癌!");

});

app.get("/readOne", function(req, res) {
    console.log("讀取單值函數(shù)");
    MyStudent.findOne({
        name: req.query.student_name
    }, {
        "id": 1,
        "_id": 0
    }, function(err, docs) {
        if (docs.id === req.query.student_id) {
            res.send('登錄成功');
            console.log(docs.password);
        } else {
            console.log(docs.password);
            res.send('登錄失敗');
        }
    });
    /*過濾查詢,參數(shù)2: {'name':1, 'password':0} 查詢文檔的返回結(jié)果包含name , 不包含password.(_id默認(rèn)是1)*/
    /*model.find({},null,{limit:20});過濾查詢,參數(shù)3: 游標(biāo)操作 limit限制返回結(jié)果數(shù)量為20個,如不足20個則返回所有*/

});

app.get("/update", function(req, res) {
    console.log("更新函數(shù)");
    MyStudent.update({
        name: "sam976"
    }, {
        id: 456,
        phone: "12345678910"
    }, function(error) {});
    res.send("更新成功!役听!");

});

app.get("/delete", function(req, res) {
    console.log("刪除函數(shù)");
    MyStudent.remove({
        name: 'sam976'
    }, function(err) {
        if (err) return handleError(err);
        // removed!
    });
    res.send("刪除成功M窍省!");

});

app.listen(3001, function() {
    console.log("start server")
});

為了測試典予,我還寫了個html甜滨。data-operate.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>data-operate</title>
</head>

<body>
    <form action="./create">
        <input type="submit" value="創(chuàng)建">
    </form>
    <br/>
    <form action="./read">
        <input type="submit" value="讀取">
    </form>
    <br/>
    <form action="./update">
        <input type="submit" value="更新">
    </form>
    <br/>
    <form action="./delete">
        <input type="submit" value="刪除">
    </form>
    <br/>
    <form action="./readOne">
        <input type="text" name="student_name">
        <input type="text" name="student_id">
        <input type="submit" value="單值讀取">
    </form>
</body>

</html>

上文是在Node中基于Mongoose對MongoDB進(jìn)行增刪查改(CRUD)操作的簡單介紹,以后會有進(jìn)階的文章瘤袖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末衣摩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子捂敌,更是在濱河造成了極大的恐慌艾扮,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件占婉,死亡現(xiàn)場離奇詭異泡嘴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)锐涯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門磕诊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人纹腌,你說我怎么就攤上這事霎终。” “怎么了升薯?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵莱褒,是天一觀的道長。 經(jīng)常有香客問我涎劈,道長广凸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任蛛枚,我火速辦了婚禮谅海,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蹦浦。我一直安慰自己扭吁,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布盲镶。 她就那樣靜靜地躺著侥袜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪溉贿。 梳的紋絲不亂的頭發(fā)上枫吧,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機(jī)與錄音宇色,去河邊找鬼九杂。 笑死,一個胖子當(dāng)著我的面吹牛宣蠕,可吹牛的內(nèi)容都是我干的例隆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼植影,長吁一口氣:“原來是場噩夢啊……” “哼裳擎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起思币,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤鹿响,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后谷饿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惶我,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年博投,在試婚紗的時候發(fā)現(xiàn)自己被綠了绸贡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖听怕,靈堂內(nèi)的尸體忽然破棺而出捧挺,到底是詐尸還是另有隱情,我是刑警寧澤尿瞭,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布闽烙,位于F島的核電站,受9級特大地震影響声搁,放射性物質(zhì)發(fā)生泄漏黑竞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一疏旨、第九天 我趴在偏房一處隱蔽的房頂上張望很魂。 院中可真熱鬧,春花似錦檐涝、人聲如沸遏匆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拉岁。三九已至,卻和暖如春惰爬,著一層夾襖步出監(jiān)牢的瞬間喊暖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工撕瞧, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留陵叽,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓丛版,卻偏偏與公主長得像巩掺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子页畦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

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