前面的話
Mongoose是在node.js非同步環境下對mongodb進行便捷操作的物件模型工具。本文將詳細介紹如何使用Mongoose來操作MongoDB
NodeJS驅動
在介紹Mongoose之前,首先介紹使用NodeJS操作MongoDB的方法
如果使用程式運算元據庫,就要使用MongoDB驅動。MongoDB驅動實際上就是為應用程式提供的一個介面,不同的語言對應不同的驅動,NodeJS驅動不能應用在其他後端語言中
首先,安裝mongodb
npm install mongodb
接著,使用require()方法引入mongodb資料庫;然後使用MongoClient物件的connect()方法連線mongodb;最後通過node來對mongodb進行非同步的增刪改查
在mongodb資料庫中建立db1資料庫,然後通過以下程式碼,建立col集合,並插入{"a":1}文件
var mongodb = require('mongodb'); mongodb.MongoClient.connect("mongodb://localhost/db1",function(err,db){ if(!err){ db.collection("col").insert({"a":1},function(err,result){ if(!err){ console.log(result); } }) } })
最後返回結果如下
{ result: { ok: 1, n: 1 }, ops: [ { a: 1, _id: 597077dc271d092728caa362 } ], insertedCount: 1, insertedIds: [ 597077dc271d092728caa362 ] }
概述
Mongoose是NodeJS的驅動,不能作為其他語言的驅動。Mongoose有兩個特點
1、通過關係型資料庫的思想來設計非關係型資料庫
2、基於mongodb驅動,簡化操作
Mongooose中,有三個比較重要的概念,分別是Schema、Model、Entity。它們的關係是:Schema生成Model,Model創造Document,Model和Document都可對資料庫操作造成影響,但Model比Document更具操作性
Schema
用於定義資料庫的結構。類似建立表時的資料定義(不僅僅可以定義文件的結構和屬性,還可以定義文件的例項方法、靜態模型方法、複合索引等),每個Schema
會對映到mongodb中的一個collection,Schema
不具備運算元據庫的能力
Model是由Schema編譯而成的構造器,具有抽象屬性和行為,可以對資料庫進行增刪查改。Model的每一個例項(instance)就是一個文件document
Document是由Model建立的實體,它的操作也會影響資料庫
安裝
安裝nodejs和mongodb之後 ,使用npm來安裝mongoose
npm install mongoose
安裝成功後,就可以通過 require('mongoose') 來使用
連線資料庫
使用require()方法在專案中包含mongoose後,接下來使用connect()方法連線到MongoDB資料庫
【connect()】
mongoose.connect(url);
connect()最簡單的使用方式,就是隻要傳入url引數即可,如下所示。連線到本地localhost的db1伺服器
mongoose.connect('mongodb://localhost/db1');
如果還需要傳遞使用者名稱、密碼,則可以使用如下方式
mongoose.connect('mongodb://username:password@host:port/database?options...');
connect()方法還接受一個選項物件options,該物件將傳遞給底層驅動程式。這裡所包含的所有選項優先於連線字串中傳遞的選項
mongoose.connect(uri, options);
可用選項如下所示
db -資料庫設定 server -伺服器設定 replset -副本集設定 user -使用者名稱 pass -密碼 auth -鑑權選項 mongos -連線多個資料庫 promiseLibrary
var options = { db: { native_parser: true }, server: { poolSize: 5 }, replset: { rs_name: 'myReplicaSetName' }, user: 'myUserName', pass: 'myPassword' } mongoose.connect(uri, options);
如果要連線多個資料庫,只需要設定多個url以,
隔開,同時設定mongos
為true
mongoose.connect('urlA,urlB,...', { mongos : true })
connect()函式還接受一個回撥引數
mongoose.connect(uri, options, function(error) { });
執行下列程式碼後,控制檯輸出“連線成功”
var mongoose = require('mongoose'); mongoose.connect("mongodb://localhost/test", function(err) { if(err){ console.log('連線失敗'); }else{ console.log('連線成功'); } });
如果開啟鑑權控制,以使用者名稱"u1",密碼"123456"登入'db1'資料庫。執行程式碼後,控制檯輸出“連線成功”
var mongoose = require('mongoose'); mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) { if(err){ console.log('連線失敗'); }else{ console.log('連線成功'); } });
【disconnect()】
mongoose.disconnect()
使用disconnect()方法可以斷開連線
var mongoose = require('mongoose'); mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) { if(err){ console.log('連線失敗'); }else{ console.log('連線成功'); } }); setTimeout(function(){ mongoose.disconnect(function(){ console.log("斷開連線"); }) }, 2000);
Schema
Schema主要用於定義MongoDB中集合Collection裡文件document的結構
定義Schema非常簡單,指定欄位名和型別即可,支援的型別包括以下8種
String 字串
Number 數字
Date 日期
Buffer 二進位制
Boolean 布林值
Mixed 混合型別
ObjectId 物件ID
Array 陣列
通過mongoose.Schema來呼叫Schema,然後使用new方法來建立schema物件
var mongoose = require('mongoose'); var Schema = mongoose.Schema; var mySchema = new Schema({ title: String, author: String, body: String, comments: [{ body: String, date: Date }], date: { type: Date, default: Date.now }, hidden: Boolean, meta: { votes: Number, favs: Number } });
[注意]建立Schema物件時,宣告欄位型別有兩種方法,一種是首字母大寫的欄位型別,另一種是引號包含的小寫欄位型別
var mySchema = new Schema({title:String, author:String}); //或者 var mySchema = new Schema({title:'string', author:'string'});
如果需要在Schema定義後新增其他欄位,可以使用add()方法
var MySchema = new Schema; MySchema.add({ name: 'string', color: 'string', price: 'number' });
【timestamps】
在schema中設定timestamps為true,schema對映的文件document會自動新增createdAt和updatedAt這兩個欄位,代表建立時間和更新時間
var UserSchema = new Schema( {...}, { timestamps: true } );
【_id】
每一個文件document都會被mongoose新增一個不重複的_id,_id的資料型別不是字串,而是ObjectID型別。如果在查詢語句中要使用_id,則需要使用findById語句,而不能使用find或findOne語句
Model
模型Model是根據Schema編譯出的構造器,或者稱為類,通過Model可以例項化出文件物件document
文件document的建立和檢索都需要通過模型Model來處理
【model()】
mongoose.model()
使用model()方法,將Schema編譯為Model。model()方法的第一個引數是模型名稱
[注意]一定要將model()方法的第一個引數和其返回值設定為相同的值,否則會出現不可預知的結果
Mongoose會將集合名稱設定為模型名稱的小寫版。如果名稱的最後一個字元是字母,則會變成複數;如果名稱的最後一個字元是數字,則不變;如果模型名稱為"MyModel",則集合名稱為"mymodels";如果模型名稱為"Model1",則集合名稱為"model1"
var schema = new mongoose.Schema({ num:Number, name: String, size: String}); var MyModel = mongoose.model('MyModel', schema);
【例項化文件document】
通過對原型Model1使用new方法,例項化出文件document物件
var mongoose = require('mongoose'); mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) { if(err){ console.log('連線失敗'); }else{ console.log('連線成功'); var schema = new mongoose.Schema({ num:Number, name: String, size: String}); var MyModel = mongoose.model('MyModel', schema); var doc1 = new MyModel({ size: 'small' }); console.log(doc1.size);//'small' } });
【文件儲存】
通過new Model1()建立的文件doc1,必須通過save()方法,才能將建立的文件儲存到資料庫的集合中,集合名稱為模型名稱的小寫複數版
回撥函式是可選項,第一個引數為err,第二個引數為儲存的文件物件
save(function (err, doc) {})
var mongoose = require('mongoose'); mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) { if(!err){ var schema = new mongoose.Schema({ num:Number, name: String, size: String }); var MyModel = mongoose.model('MyModel', schema); var doc1 = new MyModel({ size: 'small' }); doc1.save(function (err,doc) { //{ __v: 0, size: 'small', _id: 5970daba61162662b45a24a1 } console.log(doc); }) } });
由下圖所示,db1資料庫中的集合名稱為mymodels,裡面有一個{size:"small"}的文件
自定義方法
【例項方法】
Model
的例項是document,
內建例項方法有很多,如 save
,可以通過Schema物件的methods
屬性給例項自定義擴充套件方法
var mongoose = require('mongoose'); mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) { if(!err){ var schema = new mongoose.Schema({ num:Number, name: String, size: String }); schema.methods.findSimilarSizes = function(cb){ return this.model('MyModel').find({size:this.size},cb); } var MyModel = mongoose.model('MyModel', schema); var doc1 = new MyModel({ name:'doc1', size: 'small' }); var doc2 = new MyModel({ name:'doc2', size: 'small' }); var doc3 = new MyModel({ name:'doc3', size: 'big' }); doc1.save(); doc2.save(); doc3.save(); setTimeout(function(){ doc1.findSimilarSizes(function(err,docs){ docs.forEach(function(item,index,arr){ //doc1 //doc2 console.log(item.name) }) }) },0) } });
【靜態方法】
通過Schema物件的statics
屬性給 Model
新增靜態方法
var mongoose = require('mongoose'); mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) { if(!err){ var schema = new mongoose.Schema({ num:Number, name: String, size: String }); schema.statics.findByName = function(name,cb){ return this.find({name: new RegExp(name,'i')},cb); } var MyModel = mongoose.model('MyModel', schema); var doc1 = new MyModel({ name:'doc1', size: 'small' }); var doc2 = new MyModel({ name:'doc2', size: 'small' }); var doc3 = new MyModel({ name:'doc3', size: 'big' }); doc1.save(); doc2.save(); doc3.save(); setTimeout(function(){ MyModel.findByName('doc1',function(err,docs){ //[ { _id: 5971e68f4f4216605880dca2,name: 'doc1',size: 'small',__v: 0 } ] console.log(docs); }) },0) } });
由上所示,例項方法和靜態方法的區別在於,靜態方法是通過Schema物件的statics屬性
給model
新增方法,例項方法是通過Schema物件的methods
是給document新增方法
【查詢方法】
通過schema物件的query屬性,給model新增查詢方法
var mongoose = require('mongoose'); mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) { if(!err){ var schema = new mongoose.Schema({ age:Number, name: String}); schema.query.byName = function(name){ return this.find({name: new RegExp(name)}); } var temp = mongoose.model('temp', schema); temp.find().byName('huo').exec(function(err,docs){ //[ { _id: 5971f93be6f98ec60e3dc86c, name: 'huochai', age: 27 }, // { _id: 5971f93be6f98ec60e3dc86e, name: 'huo', age: 30 } ] console.log(docs); }) } });
文件新增
文件新增有三種方法,一種是使用上面介紹過的文件的save()方法,另一種是使用模型model的create()方法,最後一種是模型model的insertMany()方法
【save()】
[注意]回撥函式可以省略
save([options], [options.safe], [options.validateBeforeSave], [fn])
新建{age:10,name:'save'}文件,並儲存
var mongoose = require('mongoose'); mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) { if(!err){ var schema = new mongoose.Schema({ age:Number, name: String}); var temp = mongoose.model('temp', schema); //使用鏈式寫法 new temp({age:10,name:'save'}).save(function(err,doc){ //[ { _id: 59720bc0d2b1125cbcd60b3f, age: 10, name: 'save', __v: 0 } ] console.log(doc); }); } });
【create()】
使用save()方法,需要先例項化為文件,再使用save()方法儲存文件。而create()方法,則直接在模型Model上操作,並且可以同時新增多個文件
Model.create(doc(s), [callback])
新增{name:"xiaowang"},{name:"xiaoli"}這兩個文件
var mongoose = require('mongoose'); mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) { if(!err){ var schema = new mongoose.Schema({ age:Number, name: String}); var temp = mongoose.model('temp', schema); temp.create({name:"xiaowang"},{name:"xiaoli"},function(err,doc1,doc2){ //{ __v: 0, name: 'xiaowang', _id: 59720d83ad8a953f5cd04664 } console.log(doc1); //{ __v: 0, name: 'xiaoli', _id: 59720d83ad8a953f5cd04665 } console.log(doc2); }); } });
【insertMany()】
Model.insertMany(doc(s), [options], [callback])
新增{name:"a"},{name:"b"}這兩個文件
var mongoose = require('mongoose'); mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) { if(!err){ var schema = new mongoose.Schema({ age:Number, name: String}); var temp = mongoose.model('temp', schema); temp.insertMany([{name:"a"},{name:"b"}],function(err,docs){ //[ { __v: 0, name: 'a', _id: 59720ea1bbf5792af824b30c }, //{ __v: 0, name: 'b', _id: 59720ea1bbf5792af824b30d } ] console.log(docs); }); } });
文件查詢
使用Mongoose來查詢文件很容易,有以下3種方法可供選擇
find()
findById()
findOne()
【find()】
第一個參數列示查詢條件,第二個引數用於控制返回的欄位,第三個引數用於配置查詢引數,第四個引數是回撥函式,回撥函式的形式為function(err,docs){}
Model.find(conditions, [projection], [options], [callback])
在資料庫db1的集合temps中存在如下資料
現在,使用find()方法找出所有資料
var mongoose = require('mongoose'); mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) { if(!err){ var schema = new mongoose.Schema({ age:Number, name: String}); var temp = mongoose.model('temp', schema); temp.find(function(err,docs){ //[ { _id: 5971f93be6f98ec60e3dc86c, name: 'huochai', age: 27 }, //{ _id: 5971f93be6f98ec60e3dc86d, name: 'wang', age: 18 }, //{ _id: 5971f93be6f98ec60e3dc86e, name: 'huo', age: 30 }, //{ _id: 5971f93be6f98ec60e3dc86f, name: 'li', age: 12 } ] console.log(docs); }) } });
找出年齡大於18的資料
temp.find({age:{$gte:18}},function(err,docs){ //[ { _id: 5971f93be6f98ec60e3dc86c, name: 'huochai', age: 27 }, //{ _id: 5971f93be6f98ec60e3dc86d, name: 'wang', age: 18 }, //{ _id: 5971f93be6f98ec60e3dc86e, name: 'huo', age: 30 }] console.log(docs); })
找出年齡大於18且名字裡存在'huo'的資料
temp.find({name:/huo/,age:{$gte:18}},function(err,docs){ //[ { _id: 5971f93be6f98ec60e3dc86c, name: 'huochai', age: 27 }, //{ _id: 5971f93be6f98ec60e3dc86e, name: 'huo', age: 30 }] console.log(docs); })
找出名字裡存在'a'的資料,且只輸出'name'欄位
[注意]_id欄位預設輸出
temp.find({name:/a/},'name',function(err,docs){ //[ { _id: 5971f93be6f98ec60e3dc86c, name: 'huochai' }, //{ _id: 5971f93be6f98ec60e3dc86d, name: 'wang' } ] console.log(docs); })
如果確實不需要_id欄位輸出,可以進行如下設定
temp.find({name:/a/},{name:1,_id:0},function(err,docs){ //[ { name: 'huochai' }, { name: 'wang' } ] console.log(docs); })
找出跳過前兩條資料的其他所有資料
[注意]如果使用第三個引數,前兩個引數如果沒有值,需要設定為null
temp.find(null,null,{skip:2},function(err,docs){ //[ { _id: 5971f93be6f98ec60e3dc86e, name: 'huo', age: 30 }, //{ _id: 5971f93be6f98ec60e3dc86f, name: 'li', age: 12 } ] console.log(docs); })
【findById()】
Model.findById(id, [projection], [options], [callback])
顯示第0個元素的所有欄位
var aIDArr = []; temp.find(function(err,docs){ docs.forEach(function(item,index,arr){ aIDArr.push(item._id); }) temp.findById(aIDArr[0],function(err,doc){ //{ _id: 5971f93be6f98ec60e3dc86c, name: 'huochai', age: 27 } console.log(doc); }) })
以上程式碼的另一種寫法如下
var aIDArr = []; temp.find(function(err,docs){ docs.forEach(function(item,index,arr){ aIDArr.push(item._id); }) temp.findById(aIDArr[0]).exec(function(err,doc){ //{ _id: 5971f93be6f98ec60e3dc86c, name: 'huochai', age: 27 } console.log(doc); }) })
只輸出name欄位
temp.findById(aIDArr[0],{name:1,_id:0},function(err,doc){ //{ name: 'huochai'} console.log(doc); })
或者寫成下面這種形式
temp.findById(aIDArr[0],{name:1,_id:0}).exec(function(err,doc){ //{ name: 'huochai'} console.log(doc); })
輸出最少的欄位
temp.findById(aIDArr[0],{lean:true},function(err,doc){ //{ _id: 5971f93be6f98ec60e3dc86c } console.log(doc); }) temp.findById(aIDArr[0],{lean:true}).exec(function(err,doc){ //{ _id: 5971f93be6f98ec60e3dc86c } console.log(doc); })
【findOne()】
該方法返回查詢到的所有例項的第一個
Model.findOne([conditions], [projection], [options], [callback])
找出age>20的文件中的第一個文件
temp.findOne({age:{$gt : 20}},function(err,doc){ //{ _id: 5971f93be6f98ec60e3dc86c, name: 'huochai', age: 27 } console.log(doc); }) temp.findOne({age:{$gt : 20}}).exec(function(err,doc){ //{ _id: 5971f93be6f98ec60e3dc86c, name: 'huochai', age: 27 } console.log(doc); })
找出age>20的文件中的第一個文件,且只輸出name欄位
temp.findOne({age:{$gt : 20}},{name:1,_id:0},function(err,doc){ //{ name: 'huochai' } console.log(doc); }) temp.findOne({age:{$gt : 20}},{name:1,_id:0}).exec(function(err,doc){ //{ name: 'huochai' } console.log(doc); })
找出age>20的文件中的第一個文件,且輸出包含name欄位在內的最短欄位
temp.findOne({age:{$gt : 20}},"name",{lean:true},function(err,doc){ //{ _id: 5971f93be6f98ec60e3dc86c, name: 'huochai' } console.log(doc); }) temp.findOne({age:{$gt : 20}},"name").lean().exec(function(err,doc){ //{ _id: 5971f93be6f98ec60e3dc86c, name: 'huochai' } console.log(doc); })
文件查詢中,常用的查詢條件如下
$or 或關係 $nor 或關係取反 $gt 大於 $gte 大於等於 $lt 小於 $lte 小於等於 $ne 不等於 $in 在多個值範圍內 $nin 不在多個值範圍內 $all 匹配陣列中多個值 $regex 正則,用於模糊查詢 $size 匹配陣列大小 $maxDistance 範圍查詢,距離(基於LBS) $mod 取模運算 $near 鄰域查詢,查詢附近的位置(基於LBS) $exists 欄位是否存在 $elemMatch 匹配內陣列內的元素 $within 範圍查詢(基於LBS) $box 範圍查詢,矩形範圍(基於LBS) $center 範圍醒詢,圓形範圍(基於LBS) $centerSphere 範圍查詢,球形範圍(基於LBS) $slice 查詢欄位集合中的元素(比如從第幾個之後,第N到第M個元素
【$where】
如果要進行更復雜的查詢,需要使用$where操作符,$where操作符功能強大而且靈活,它可以使用任意的JavaScript作為查詢的一部分,包含JavaScript表示式的字串或者JavaScript函式
使用字串
temp.find({$where:"this.x == this.y"},function(err,docs){ //[ { _id: 5972ed35e6f98ec60e3dc887,name: 'wang',age: 18,x: 1,y: 1 }, //{ _id: 5972ed35e6f98ec60e3dc889, name: 'li', age: 20, x: 2, y: 2 } ] console.log(docs); })
temp.find({$where:"obj.x == obj.y"},function(err,docs){ //[ { _id: 5972ed35e6f98ec60e3dc887,name: 'wang',age: 18,x: 1,y: 1 }, //{ _id: 5972ed35e6f98ec60e3dc889, name: 'li', age: 20, x: 2, y: 2 } ] console.log(docs); })
使用函式
temp.find({$where:function(){ return obj.x !== obj.y; }},function(err,docs){ //[ { _id: 5972ed35e6f98ec60e3dc886,name: 'huochai',age: 27,x: 1,y: 2 }, //{ _id: 5972ed35e6f98ec60e3dc888, name: 'huo', age: 30, x: 2, y: 1 } ] console.log(docs); })
temp.find({$where:function(){ return this.x !== this.y; }},function(err,docs){ //[ { _id: 5972ed35e6f98ec60e3dc886,name: 'huochai',age: 27,x: 1,y: 2 }, //{ _id: 5972ed35e6f98ec60e3dc888, name: 'huo', age: 30, x: 2, y: 1 } ] console.log(docs); })
文件更新
文件更新可以使用以下幾種方法
update()
updateMany()
find() + save()
updateOne()
findOne() + save()
findByIdAndUpdate()
fingOneAndUpdate()
【update()】
第一個引數conditions為查詢條件,第二個引數doc為需要修改的資料,第三個引數options為控制選項,第四個引數是回撥函式
Model.update(conditions, doc, [options], [callback])
options有如下選項
safe (boolean): 預設為true。安全模式。 upsert (boolean): 預設為false。如果不存在則建立新記錄。 multi (boolean): 預設為false。是否更新多個查詢記錄。 runValidators: 如果值為true,執行Validation驗證。 setDefaultsOnInsert: 如果upsert選項為true,在新建時插入文件定義的預設值。 strict (boolean): 以strict模式進行更新。 overwrite (boolean): 預設為false。禁用update-only模式,允許覆蓋記錄。
資料庫temps中現有資料如下
現在使用update()方法查詢age大於20的資料,並將其年齡更改為40歲
var mongoose = require('mongoose'); mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) { if(!err){ var schema = new mongoose.Schema({ age:Number, name: String}); var temp = mongoose.model('temp', schema); temp.update({age:{$gte:20}},{age:40},function(err,raw){ //{ n: 1, nModified: 1, ok: 1 } console.log(raw); }) } });
經過以上操作,資料庫結果如下。只有第一個資料更改為40歲。而第三個資料沒有發生變化
如果要同時更新多個記錄,需要設定options裡的multi為true。下面將名字中有'a'字元的年齡設定為10歲
var mongoose = require('mongoose'); mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) { if(!err){ var schema = new mongoose.Schema({ age:Number, name: String}); var temp = mongoose.model('temp', schema); temp.update({name:/a/},{age: 10},{multi:true},function(err,raw){ //{ n: 2, nModified: 2, ok: 1 } console.log(raw); }) } });
如果設定的查詢條件,資料庫裡的資料並不滿足,預設什麼事都不發生
temp.update({age:100},{name: "hundred"},function(err,raw){ //{ n: 0, nModified: 0, ok: 1 } console.log(raw); })
如果設定options裡的upsert引數為true,若沒有符合查詢條件的文件,mongo將會綜合第一第二個引數向集合插入一個新的文件
temp.update({age:100},{name: "hundred"},{upsert:true},function(err,raw){ //{ n: 1, nModified: 0,upserted: [ { index: 0, _id: 5972c202d46b621fca7fc8c7 } ], ok: 1 } console.log(raw); })
temp.update({name:/aa/},{age: 0},{upsert:true},function(err,raw){ //{ n: 1, nModified: 0,upserted: [ { index: 0, _id: 5972c288d46b621fca7fdd8f } ], ok: 1 } console.log(raw); })
[注意]update()方法中的回撥函式不能省略,否則資料不會被更新。如果回撥函式裡並沒有什麼有用的資訊,則可以使用exec()簡化程式碼
temp.update({name:/aa/},{age: 0},{upsert:true}).exec();
【updateMany()】
updateMany()與update()方法唯一的區別就是預設更新多個文件,即使設定{multi:false}也無法只更新第一個文件
Model.updateMany(conditions, doc, [options], [callback])
將資料庫中名字中帶有'huo'的資料,年齡變為50歲
temp.updateMany({name:/huo/},{age:50},function(err,raw){ //{ n: 2, nModified: 2, ok: 1 } console.log(raw); });
【find() + save()】
如果需要更新的操作比較複雜,可以使用find()+save()方法來處理,比如找到年齡小於30歲的資料,名字後面新增'30'字元
temp.find({age:{$lt:20}},function(err,docs){ //[ { _id: 5971f93be6f98ec60e3dc86d, name: 'wang', age: 10 }, //{ _id: 5971f93be6f98ec60e3dc86f, name: 'li', age: 12 }] console.log(docs); docs.forEach(function(item,index,arr){ item.name += '30'; item.save(); }) //[ { _id: 5971f93be6f98ec60e3dc86d, name: 'wang30', age: 10 }, // { _id: 5971f93be6f98ec60e3dc86f, name: 'li30', age: 12 }] console.log(docs); });
【updateOne()】
updateOne()方法只能更新找到的第一條資料,即使設定{multi:true}也無法同時更新多個文件
將資料庫中名字中帶有'huo'的資料,年齡變為60歲
temp.updateOne({name:/huo/},{age:60},function(err,raw){ //{ n: 1, nModified: 1, ok: 1 } console.log(raw); });
【findOne() + save()】
如果需要更新的操作比較複雜,可以使用findOne()+save()方法來處理,比如找到名字為'huochai'的資料,年齡加100歲
temp.findOne({name:'huochai'},function(err,doc){ //{ _id: 5971f93be6f98ec60e3dc86c, name: 'huochai', age: 10 } console.log(doc); doc.age += 100; doc.save(); //{ _id: 5971f93be6f98ec60e3dc86c, name: 'huochai', age: 110 } console.log(doc); });
【findOneAndUpdate()】
fineOneAndUpdate()方法的第四個引數回撥函式的形式如下function(err,doc){}
Model.findOneAndUpdate([conditions], [update], [options], [callback])
【findByIdAndUpdate】
fineByIdAndUpdate()方法的第四個引數回撥函式的形式如下function(err,doc){}
Model.findOneAndUpdate([conditions], [update], [options], [callback])
文件刪除
有三種方法用於文件刪除
remove()
findOneAndRemove()
findByIdAndRemove()
【remove()】
remove有兩種形式,一種是文件的remove()方法,一種是Model的remove()方法
下面介紹Model的remove()方法,該方法的第一個引數conditions為查詢條件,第二個引數回撥函式的形式如下function(err){}
model.remove(conditions, [callback])
刪除資料庫中名稱包括'30'的資料
temp.remove({name:/30/},function(err){})
[注意]remove()方法中的回撥函式不能省略,否則資料不會被刪除。當然,可以使用exec()方法來簡寫程式碼
temp.remove({name:/30/}).exec()
下面介紹文件的remove()方法,該方法的引數回撥函式的形式如下function(err,doc){}
document.remove([callback])
刪除資料庫中名稱包含'huo'的資料
[注意]文件的remove()方法的回撥函式引數可以省略
temp.find({name:/huo/},function(err,doc){ doc.forEach(function(item,index,arr){ item.remove(function(err,doc){ //{ _id: 5971f93be6f98ec60e3dc86c, name: 'huochai', age: 30 } //{ _id: 5971f93be6f98ec60e3dc86e, name: 'huo', age: 60 } console.log(doc); }) }) })
【findOneAndRemove()】
model的remove()會刪除符合條件的所有資料,如果只刪除符合條件的第一條資料,則可以使用model的findOneAndRemove()方法
Model.findOneAndRemove(conditions, [options], [callback])
集合temps現有資料如下
現在刪除第一個年齡小於20的資料
temp.findOneAndRemove({age:{$lt:20}},function(err,doc){ //{ _id: 5972d3f3e6f98ec60e3dc873, name: 'wang', age: 18 } console.log(doc); })
與model的remove()方法相同,回撥函式不能省略,否則資料不會被刪除。當然,可以使用exec()方法來簡寫程式碼
temp.findOneAndRemove({age:{$lt:20}}).exec()
【findByIdAndRemove()】
Model.findByIdAndRemove(id, [options], [callback])
刪除第0個元素
var aIDArr = []; temp.find(function(err,docs){ docs.forEach(function(item,index,arr){ aIDArr.push(item._id); }) temp.findByIdAndRemove(aIDArr[0],function(err,doc){ //{ _id: 5972d754e6f98ec60e3dc882, name: 'huochai', age: 27 } console.log(doc); }) })
類似的,該方法也不能省略回撥函式,否則資料不會被刪除。當然,可以使用exec()方法來簡寫程式碼
var aIDArr = []; temp.find(function(err,docs){ docs.forEach(function(item,index,arr){ aIDArr.push(item._id); }) temp.findByIdAndRemove(aIDArr[0]).exec() })
前後鉤子
前後鉤子即pre()和post()方法,又稱為中介軟體,是在執行某些操作時可以執行的函式。中介軟體在schema上指定,類似於靜態方法或例項方法等
可以在資料庫執行下列操作時,設定前後鉤子
init
validate
save
remove
count
find
findOne
findOneAndRemove
findOneAndUpdate
insertMany
update
【pre()】
以find()方法為例,在執行find()方法之前,執行pre()方法
var schema = new mongoose.Schema({ age:Number, name: String,x:Number,y:Number}); schema.pre('find',function(next){ console.log('我是pre方法1'); next(); }); schema.pre('find',function(next){ console.log('我是pre方法2'); next(); }); var temp = mongoose.model('temp', schema); temp.find(function(err,docs){ console.log(docs[0]); }) /* 我是pre方法1 我是pre方法2 { _id: 5972ed35e6f98ec60e3dc886,name: 'huochai',age: 27,x: 1,y: 2 } */
【post()】
post()方法並不是在執行某些操作後再去執行的方法,而在執行某些操作前最後執行的方法,post()方法裡不可以使用next()
var schema = new mongoose.Schema({ age:Number, name: String,x:Number,y:Number}); schema.post('find',function(docs){ console.log('我是post方法1'); }); schema.post('find',function(docs){ console.log('我是post方法2'); }); var temp = mongoose.model('temp', schema); temp.find(function(err,docs){ console.log(docs[0]); }) /* 我是post方法1 我是post方法2 { _id: 5972ed35e6f98ec60e3dc886,name: 'huochai',age: 27,x: 1,y: 2 } */
查詢後處理
常用的查詢後處理的方法如下所示
sort 排序
skip 跳過
limit 限制
select 顯示欄位
exect 執行
count 計數
distinct 去重
var schema = new mongoose.Schema({ age:Number, name: String,x:Number,y:Number}); var temp = mongoose.model('temp', schema); temp.find(function(err,docs){ //[ { _id: 5972ed35e6f98ec60e3dc886,name: 'huochai',age: 27,x: 1,y: 2 }, //{ _id: 5972ed35e6f98ec60e3dc887,name: 'wang',age: 18,x: 1,y: 1 }, //{ _id: 5972ed35e6f98ec60e3dc888, name: 'huo', age: 30, x: 2, y: 1 }, //{ _id: 5972ed35e6f98ec60e3dc889, name: 'li', age: 20, x: 2, y: 2 } ] console.log(docs); })
【sort()】
按age從小到大排序
temp.find().sort("age").exec(function(err,docs){ //[ { _id: 5972ed35e6f98ec60e3dc887,name: 'wang',age: 18,x: 1,y: 1 }, //{ _id: 5972ed35e6f98ec60e3dc889, name: 'li', age: 20, x: 2, y: 2 }, //{ _id: 5972ed35e6f98ec60e3dc886,name: 'huochai',age: 27,x: 1,y: 2 }, //{ _id: 5972ed35e6f98ec60e3dc888, name: 'huo', age: 30, x: 2, y: 1 } ] console.log(docs); });
按x從小到大,age從大到小排列
temp.find().sort("x -age").exec(function(err,docs){ //[ { _id: 5972ed35e6f98ec60e3dc886,name: 'huochai',age: 27,x: 1,y: 2 }, //{ _id: 5972ed35e6f98ec60e3dc887,name: 'wang',age: 18,x: 1,y: 1 }, //{ _id: 5972ed35e6f98ec60e3dc888, name: 'huo', age: 30, x: 2, y: 1 }, //{ _id: 5972ed35e6f98ec60e3dc889, name: 'li', age: 20, x: 2, y: 2 } ] console.log(docs); });
【skip()】
跳過1個,顯示其他
temp.find().skip(1).exec(function(err,docs){ //[ { _id: 5972ed35e6f98ec60e3dc887,name: 'wang',age: 18,x: 1,y: 1 }, //{ _id: 5972ed35e6f98ec60e3dc888, name: 'huo', age: 30, x: 2, y: 1 }, //{ _id: 5972ed35e6f98ec60e3dc889, name: 'li', age: 20, x: 2, y: 2 } ] console.log(docs); });
【limit()】
顯示2個
temp.find().limit(2).exec(function(err,docs){ //[ { _id: 5972ed35e6f98ec60e3dc886,name: 'huochai',age: 27,x: 1,y: 2 }, //{ _id: 5972ed35e6f98ec60e3dc887,name: 'wang',age: 18,x: 1,y: 1 } ] console.log(docs); });
【select()】
顯示name、age欄位,不顯示_id欄位
temp.find().select("name age -_id").exec(function(err,docs){ //[ { name: 'huochai', age: 27 },{ name: 'wang', age: 18 },{ name: 'huo', age: 30 },{ name: 'li', age: 20 } ] console.log(docs); });
temp.find().select({name:1, age:1, _id:0}).exec(function(err,docs){ //[ { name: 'huochai', age: 27 },{ name: 'wang', age: 18 },{ name: 'huo', age: 30 },{ name: 'li', age: 20 } ] console.log(docs); });
下面將以上方法結合起來使用,跳過第1個後,只顯示2個資料,按照age由大到小排序,且不顯示_id欄位
temp.find().skip(1).limit(2).sort("-age").select("-_id").exec(function(err,docs){ //[ { name: 'huochai', age: 27, x: 1, y: 2 }, //{ name: 'li', age: 20, x: 2, y: 2 } ] console.log(docs); });
【count()】
顯示集合temps中的文件數量
temp.find().count(function(err,count){ console.log(count);//4 });
【distinct()】
返回集合temps中的x的值
temp.find().distinct('x',function(err,distinct){ console.log(distinct);//[ 1, 2 ] });
文件驗證
為什麼需要文件驗證呢?以一個例子作為說明,schema進行如下定義
var schema = new mongoose.Schema({ age:Number, name: String,x:Number,y:Number});
如果不進行文件驗證,儲存文件時,就可以不按照Schema設定的欄位進行設定,分為以下幾種情況
1、缺少欄位的文件可以儲存成功
var temp = mongoose.model('temp', schema); new temp({age:10}).save(function(err,doc){ //{ __v: 0, age: 10, _id: 597304442b70086a1ce3cf05 } console.log(doc); });
2、包含未設定的欄位的文件也可以儲存成功,未設定的欄位不被儲存
new temp({age:100,abc:"abc"}).save(function(err,doc){ //{ __v: 0, age: 100, _id: 5973046a2bb57565b474f48b } console.log(doc); });
3、包含欄位型別與設定不同的欄位的文件也可以儲存成功,不同欄位型別的欄位被儲存為設定的欄位型別
new temp({age:true,name:10}).save(function(err,doc){ //{ __v: 0, age: 1, name: '10', _id: 597304f7a926033060255366 } console.log(doc); });
而通過文件驗證,就可以避免以下幾種情況發生
文件驗證在SchemaType中定義,格式如下
{name: {type:String, validator:value}}
常用驗證包括以下幾種
required: 資料必須填寫 default: 預設值 validate: 自定義匹配 min: 最小值(只適用於數字) max: 最大值(只適用於數字) match: 正則匹配(只適用於字串) enum: 列舉匹配(只適用於字串)
【required】
將age設定為必填欄位,如果沒有age欄位,文件將不被儲存,且出現錯誤提示
var schema = new mongoose.Schema({ age:{type:Number,required:true}, name: String,x:Number,y:Number}); var temp = mongoose.model('temp', schema); new temp({name:"abc"}).save(function(err,doc){ //Path `age` is required. console.log(err.errors['age'].message); });
【default】
設定age欄位的預設值為18,如果不設定age欄位,則會取預設值
var schema = new mongoose.Schema({ age:{type:Number,default:18}, name:String,x:Number,y:Number}); var temp = mongoose.model('temp', schema); new temp({name:'a'}).save(function(err,doc){ //{ __v: 0, name: 'a', _id: 59730d2e7a751d81582210c1, age: 18 } console.log(doc); });
【min | max】
將age的取值範圍設定為[0,10]。如果age取值為20,文件將不被儲存,且出現錯誤提示
var schema = new mongoose.Schema({ age:{type:Number,min:0,max:10}, name: String,x:Number,y:Number}); var temp = mongoose.model('temp', schema); new temp({age:20}).save(function(err,doc){ //Path `age` (20) is more than maximum allowed value (10). console.log(err.errors['age'].message); });
【match】
將name的match設定為必須存在'a'字元。如果name不存在'a',文件將不被儲存,且出現錯誤提示
var schema = new mongoose.Schema({ age:Number, name:{type:String,match:/a/},x:Number,y:Number}); var temp = mongoose.model('temp', schema); new temp({name:'bbb'}).save(function(err,doc){ //Path `name` is invalid (bbb). console.log(err.errors['name'].message); });
【enum】
將name的列舉取值設定為['a','b','c'],如果name不在列舉範圍內取值,文件將不被儲存,且出現錯誤提示
var schema = new mongoose.Schema({ age:Number, name:{type:String,enum:['a','b','c']},x:Number,y:Number}); var temp = mongoose.model('temp', schema); new temp({name:'bbb'}).save(function(err,doc){ //`bbb` is not a valid enum value for path `name`. console.log(err.errors['name'].message); });
【validate】
validate實際上是一個函式,函式的引數代表當前欄位,返回true表示通過驗證,返回false表示未通過驗證。利用validate可以自定義任何條件。比如,定義名字name的長度必須在4個字元以上
var validateLength = function(arg){ if(arg.length > 4){ return true; } return false; }; var schema = new mongoose.Schema({ name:{type:String,validate:validateLength}, age:Number,x:Number,y:Number}); var temp = mongoose.model('temp', schema); new temp({name:'abc'}).save(function(err,doc){ //Validator failed for path `name` with value `abc` console.log(err.errors['name'].message); });
聯表操作
下面以一個例項的形式來介紹下mongoose中的聯表操作population
以類別category和文章post之間的關聯為例
其中,category的model如下所示
const mongoose = require('mongoose') const Schema = mongoose.Schema const CategorySchema = new Schema( { number: { type: Number, required: true, index: true, unique: true, min:[1000000000, '位數不足'], max: [9999999999, '位數過長'] }, name: { type: String, required: true, validate: { validator: (v) => v.trim().length, message: '名稱不能為空'} }, description: { type: String }, posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }], recommend: { type: Boolean }, index: { type: Number } }, { timestamps: true } ) module.exports = mongoose.model('Category', CategorySchema)
post的model如下所示
const mongoose = require('mongoose') const Schema = mongoose.Schema const PostSchema = new Schema( { title: { type: String, required: true, unique: true }, description: { type: String }, content: { type: String }, category: { type: Schema.Types.ObjectId, ref: 'Category', index: true }, comments: [{ type: Schema.Types.ObjectId, ref: 'Comment' }], likes: [{ type: Schema.Types.ObjectId, ref: 'Like' }], imgUrl: { type: String }, recommend: { type: Boolean }, index: { type: Number } }, { timestamps: true } ) module.exports = mongoose.model('Post', PostSchema)
在對類別的操作中, 都需要使用populate操作符顯示出所包括的posts中的title
/* 載入所有類別 */ app.get('/categories', (req, res) => { Category.find().populate('posts','title').select("number name description recommend index").exec((err, docs) => { if (err) return res.status(500).json({code: 0, message: err.message, err}) return res.status(200).json({code: 1, message: '獲取類別成功', result: {docs}}) }) }) /* 新增一個類別 */ app.post('/categories', adminAuth, (req, res) => { new Category(req.body).save((err, doc) => { if (err) return res.status(500).json({code: 0, message: err.message, err}) doc.populate({path:'posts',select:'title'}, (err, doc) => { if (err) return res.status(500).json({code:0, message: err.message, err}) return res.status(200).json({code: 1, message: '新增成功', result: {doc}}) }) }) }) ...
在對文章的操作中,則需要顯示出類別category的number屬性
/* 按照id載入一篇文章 */ app.get('/posts/:id', (req, res) => { Post.findById(req.params.id).populate('category','number').exec((err, doc) => { if (err) return res.status(500).json({code:0, message:err.message, err}) if (doc === null) return res.status(404).json({code:0, message:'文章不存在'}) return res.status(200).json({code:1, message:'獲取文章成功', result:{doc}}) }) }) /* 載入所有文章 */ app.get('/posts', (req, res) => { Post.find().select("title likes comments recommend imgUrl index").populate('category','number').sort("-createdAt").exec((err, docs) => { if (err) return res.status(500).json({code: 0, message: err.message, err}) return res.status(200).json({code: 1, message: '獲取文章成功', result: {docs}}) })
在新增、更新和刪除文章的操作中,都需要重建與category的關聯
// 關聯category的posts陣列 fnRelatedCategory = _id => { Category.findById(_id).exec((err, categoryDoc) => { if (err) return res.status(500).json({ code: 0, message: err.message, err }) if (categoryDoc === null) return res.status(404).json({code:0, message:'該類別不存在,請重新整理後再試'}) Post.find({ category: _id }).exec((err, postsDocs) => { if (err) return res.status(500).json({ code: 0, message: err.message, err }) categoryDoc.posts = postsDocs.map(t => t._id) categoryDoc.save(err => { if (err) return res.status(500).json({ code: 0, message: err.message, err }) }) }) }) } /* 按照id更新一篇文章 */ app.put('/posts/:id', adminAuth, (req, res) => { Post.findById(req.params.id).exec((err, doc) => { if (err) return res.status(500).json({code: 0, message: err.message, err}) if (doc === null) return res.status(404).json({code: 0, message: '文章不存在,請重新整理後再試'}) for (prop in req.body) { doc[prop] = req.body[prop] } doc.save((err) => { if (err) return res.status(500).json({code: 0, message: err.message, err}) doc.populate({path:'category',select:'number'}, (err, doc) => { if (err) return res.status(500).json({code:0, message: err.message, err}) fnRelatedCategory(doc.category._id) return res.status(200).json({code: 1, message: '更新成功', result: {doc}}) }) }) }) }) ...
最後
mongoose操作基礎入門大致就是以上這些。mongoose的很多操作與mongodb的操作命令非常類似,學起來並不難。但是,由於中文資源並不完善,需要對照英文文件進行學習,可能會稍顯吃力。而且,mongoose對mongodb做了許多擴充套件,增加了許多方法,需要更多耐心
歡迎交流