一. 介紹
-
MongoDB
是文檔型數(shù)據(jù)庫(Document Database
)责球,不是關(guān)系型數(shù)據(jù)庫(Relational Database
)淮韭。而Mongoose
可以將MongonDB
數(shù)據(jù)庫存儲的文檔(documents
)轉(zhuǎn)化為javascript
對象毫胜,然后可以直接進(jìn)行數(shù)據(jù)的增刪改查渴语。
注釋:Mongoose
是在node.js
異步環(huán)境下對mongodb
進(jìn)行便捷操作的對象模型工具懂衩。Mongoose
只能作為NodeJS
的驅(qū)動(dòng)脯倒,不能作為其他語言的驅(qū)動(dòng)。 -
Mongooose
中有三個(gè)比較重要的概念漱病,分別是Schema
买雾、Model
、entity
杨帽。它們的關(guān)系是Schema
生成Model
漓穿,Model
實(shí)例出entity
。 -
Schema
不具備操作數(shù)據(jù)庫的能力注盈。Model
和entity
都可對數(shù)據(jù)庫操作造成影響晃危,Model
比entity
更具操作性。 -
Schema
用于定義數(shù)據(jù)庫的結(jié)構(gòu)。每個(gè)Schema
都會(huì)映射到mongodb
中的一個(gè)collection
僚饭。 -
Model
是由Schema
編譯而成的構(gòu)造器震叮,具有抽象屬性和行為,可以對數(shù)據(jù)庫進(jìn)行增刪查改鳍鸵。Model
的每一個(gè)實(shí)例entity
就是一個(gè)文檔document
苇瓣。
注釋:Schema
對應(yīng)數(shù)據(jù)庫中一個(gè)集合collection
里文檔document
的數(shù)據(jù)結(jié)構(gòu)。Model
對應(yīng)一個(gè)集合collection
偿乖。entity
是Model
的實(shí)例击罪,對應(yīng)集合collection
中的一個(gè)文檔document
。
二. 連接數(shù)據(jù)庫
- 使用
connect()
方法連接數(shù)據(jù)庫贪薪。
語法:mongoose.connect(url, options)
參數(shù):options
為可選參數(shù)媳禁,優(yōu)先級高于url
。options
可用選項(xiàng)如下:
選項(xiàng) | 含義 |
---|---|
db | 數(shù)據(jù)庫設(shè)置 |
server | 服務(wù)器設(shè)置 |
replset | 副本集設(shè)置 |
user | 用戶名 |
pass | 密碼 |
auth | 鑒權(quán)選項(xiàng) |
mongos | 連接多個(gè)數(shù)據(jù)庫 |
- 簡單連接画切,傳入
url
參數(shù)以及db
數(shù)據(jù)庫名稱竣稽。
mongoose.connect('mongodb://127.0.0.1/test');
注意:默認(rèn)端口為27017
。
- 傳入用戶名槽唾、密碼丧枪、端口等參數(shù)光涂。
mongoose.connect('mongodb://username:password@host:port/database?options...');
- 通過
mongoose.connection
監(jiān)聽連接狀態(tài)庞萍。
var mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1:27017/test');
mongoose.connection.on('connected', function() {
console.log('MongoDB connected connected');
})
mongoose.connection.on('error', function() {
console.log('MongoDB connected error');
})
mongoose.connection.on('disconnected', function() {
console.log('MongoDB connected disconnected');
})
- 避免多次連接
新建一個(gè)mongoose.js
:
var mongoose = require("mongoose");
mongoose.connect('mongodb://127.0.0.1:27017/test');
module.exports = mongoose;
每個(gè)module
中,引用var mongoose = require('./mongoose.js')
忘闻。
- 斷開連接
語法:mongoose.disconnect()
mongoose.connect('mongodb://127.0.0.1:27017/test');
setTimeout(()=> {
mongoose.disconnect();
}, 2000);
mongoose.connection.on('connected', function() {
console.log('MongoDB connected connected');
})
mongoose.connection.on('error', function() {
console.log('MongoDB connected error');
})
mongoose.connection.on('disconnected', function() {
console.log('MongoDB connected disconnected');
})
三. Scheme
-
Schema
主要用于定義MongoDB
中集合collection
里文檔document
的結(jié)構(gòu)钝计。定義Schema
非常簡單,指定字段名和類型即可齐佳。 -
Scheme
支持以下8種類型
String
字符串私恬、Number
數(shù)字 、Date
日期炼吴、Buffer
二進(jìn)制本鸣、Boolean
布爾值、Mixed
混合類型硅蹦、ObjectId
對象ID荣德、Array
數(shù)組。 - 通過
mongoose.Schema
來調(diào)用Schema
童芹,然后使用new
方法來創(chuàng)建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
}
});
- 創(chuàng)建
Schema
對象時(shí),聲明字段類型有兩種方法假褪,一種是首字母大寫的字段類型署咽,另一種是引號包含的小寫字段類型。
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' });
- 在
schema
中設(shè)置timestamps
為true
窒升,schema
映射的文檔document
會(huì)自動(dòng)添加createdAt
和updatedAt
這兩個(gè)字段,代表創(chuàng)建時(shí)間和更新時(shí)間慕匠。
var UserSchema = new Schema(
{...},
{ timestamps: true }
);
- 每一個(gè)文檔
document
都會(huì)被mongoose
添加一個(gè)不重復(fù)的_id
异剥,_id
的數(shù)據(jù)類型不是字符串,而是ObjectID
類型絮重。如果在查詢語句中要使用_id
冤寿,則需要使用findById
語句,而不能使用find
或findOne
語句青伤。
四. Model
- 模型
Model
是根據(jù)Schema
編譯出的構(gòu)造器督怜,或者稱為類。 - 使用
model()
方法將Schema
編譯為Model
語法:mongoose.model("模型名稱", Scheme, "Collection名稱(可選)")
注意:如果未傳第三個(gè)參數(shù)指定Collection
集合名稱狠角。則Mongoose
會(huì)將Collection
集合名稱設(shè)置為模型名稱的小寫版号杠。如果名稱的最后一個(gè)字符是字母,則會(huì)變成復(fù)數(shù)丰歌;如果名稱的最后一個(gè)字符是數(shù)字姨蟋,則不變。例如立帖,如果模型名稱為MyModel
眼溶,則集合名稱為mymodels
;如果模型名稱為Model1
晓勇,則集合名稱為model1
堂飞。
var schema = new mongoose.Schema({ num:Number, name: String, size: String});
var MyModel = mongoose.model('MyModel', schema);
- 生成實(shí)例
entity
var schema = new mongoose.Schema({ num:Number, name: String, size: String});
var MyModel = mongoose.model('MyModel', schema);
var doc = new MyModel({ size: 'small' });
console.log(doc.size);//'small'
- 實(shí)例
entity
保存為文檔document
通過new Model()
創(chuàng)建的實(shí)例entity
,必須通過save()
方法绑咱,才能將對應(yīng)文檔document
保存到數(shù)據(jù)庫的集合collection
中绰筛。回調(diào)函數(shù)是可選項(xiàng)描融,第一個(gè)參數(shù)為err
铝噩,第二個(gè)參數(shù)為保存的文檔document
對象。
語法:save(function (err, doc) {})
var mongoose = require('mongoose');
mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
var schema = new mongoose.Schema({ num:Number, name: String, size: String});
var MyModel = mongoose.model('MyModel', schema);
var doc = new MyModel({ size: 'small' });
doc.save(function (err,doc) {
//{ __v: 0, size: 'small', _id: 5970daba61162662b45a24a1 }
console.log(doc);
})
注釋:① 一個(gè)實(shí)例Entiy
對應(yīng)一條文檔document
窿克。② 實(shí)例entity
的save
方法只能夠在文檔document
中保存scheme
結(jié)構(gòu)范圍內(nèi)的字段骏庸。
五. 自定義方法
- 實(shí)例方法
Model
的實(shí)例entity
有很多內(nèi)置方法,例如save
让歼〕担可以通過Schema
對象的methods
屬性給entity
自定義擴(kuò)展方法。
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)
- 靜態(tài)方法
可以通過Schema
對象的statics
屬性給Model
添加靜態(tài)方法谋右。
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)
注釋:靜態(tài)方法是通過Schema
對象的statics
屬性給model
添加方法硬猫;實(shí)例方法是通過Schema
對象的methods
是給entity
添加方法。
- 查詢方法
通過schema
對象的query
屬性,給model
添加查詢方法啸蜜。
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);
})
注釋:查詢方法需要有 .find()
作為開頭坑雅。
問題:靜態(tài)方法與查詢方法的本質(zhì)區(qū)別?
六. 新增文檔
-
mongoose
提供了三種新增文檔document
的方法:
(1)entity
的save()
方法
(2)model
的create()
方法
(3)model
的insertMany()
方法 -
entity
的save()
方法
save([options], [options.safe], [options.validateBeforeSave], [fn])
mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
var schema = new mongoose.Schema({name: String, age: Number});
var model = mongoose.model('temp', schema);
var entity = new model({name: 'john', age: 18});
entity.save((err, doc) => {
//{ _id: 5b63a49e8910503426acd587, name: 'john', age: 18, __v: 0 }
console.log(doc);
})
-
model
的create()
方法
使用save()
方法衬横,需要先實(shí)例化為entity
裹粤,再使用save()
方法保存文檔document
。而create()
方法直接在模型model
上操作蜂林,并且可以同時(shí)新增多個(gè)文檔document
遥诉。
Model.create(doc(s), [callback])
mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
var schema = new mongoose.Schema({name: String, age: Number});
var model = mongoose.model('temp', schema);
model.create([{name:"lily"}, {name:'jane'}],(err, docs) => {
console.log(docs);
//[ { _id: 5b643cc96ca76572d64e242c, name: 'lily', __v: 0 },
//{ _id: 5b643cc96ca76572d64e242d, name: 'jane', __v: 0 } ]
});
-
model
的insertMany()
方法
mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
var schema = new mongoose.Schema({name: String, age: Number});
var model = mongoose.model('temp', schema);
model.insertMany([{name:"a"}, {name:"b"}],(err, docs) => {
console.log(docs);
//[ { _id: 5b643db39d69e1731042a0f1, name: 'a', __v: 0 },
// { _id: 5b643db39d69e1731042a0f2, name: 'b', __v: 0 } ]
});
注意:新增文檔方法的callback
回調(diào)函數(shù)不能使用exec
方法改寫。查詢文檔噪叙、更新文檔以及刪除文檔方法的callback
回調(diào)函數(shù)大多數(shù)都可以使用exec
方法改寫矮锈。
問題:model
的create()
方法與insertMany()
方法的區(qū)別。
七. 查詢文檔
-
mongoose
提供了三種查詢文檔document
的方法:
(1)find()
(2)findById()
(3)findOne()
-
find()
方法
Model.find(conditions, [projection], [options], [callback])
mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
var schema = new mongoose.Schema({name: String, age: Number});
var model = mongoose.model('temp', schema);
for(let i = 1; i < 10; i++) {
new model({name: `jake${i}`, age: i}).save();
}
//查找所有文檔
model.find((err, docs) => {
console.log(docs);//1,2,3,4,5,6,7,8,9
});
//查找年齡大于等于6的文檔
model.find({age: {$gte: 6}}, (err, docs) => {
console.log(docs); //6睁蕾,7苞笨,8,9
});
//查找年齡大于等于6文檔的另一種寫法
model.find({age: {$gte: 6}}).exec((err, docs) => {
console.log(docs); //6子眶,7瀑凝,8,9
});
//年齡大于8,且名字存在'jake'的數(shù)據(jù)
model.find({age: {$gt: 8}, name: /jake/}, (err, docs) => {
console.log(docs); //9
});
//年齡等于1臭杰,且只輸出'name'字段
model.find({age: {$lt: 3}}, 'name', (err, docs) => {
console.log(docs);
//[ { _id: 5b644b5a43048277c40834c6, name: 'jake1' },
//{ _id: 5b644b5a43048277c40834c7, name: 'jake2' } ]
});
//年齡等于1粤咪,且不需要輸出_id
model.find({age: 1}, {name: 1, _id: 0}, (err, docs) => {
console.log(docs); //[ { name: 'jake1' } ]
});
//搜索年齡大于2,且搜索結(jié)果跳過前2條, 只留3條硅卢,且按年齡倒序
model.find({age: {$gt: 2}}).skip(2).limit(3).sort({age: -1}).exec((err, docs) => {
console.log(docs); //7,6,5
});
注釋: 可參考mongo.exe
程序以及node
原生查詢mongodb
數(shù)據(jù)庫API
射窒,接口類似。
注意:① 兩種寫法find(..., callback)
和find(...).exec(callback)
将塑。② node
查詢通過toArray(err, docs)
方法獲取文檔數(shù)組,mongoose
查詢通過exec(err, docs)
方法獲取文檔數(shù)組蝌麸。
-
findById()
方法
Model.findById(id, [projection], [options], [callback])
model.findById('5b644b5a43048277c40834c7', (err, doc) => {
console.log(doc);
//{ _id: 5b644b5a43048277c40834c7, name: 'jake2', age: 2, __v: 0 }
});
//另一種寫法
model.findById('5b644b5a43048277c40834c7').exec((err, doc) => {
console.log(doc);
//{ _id: 5b644b5a43048277c40834c7, name: 'jake2', age: 2, __v: 0 }
});
//只輸出name字段
model.findById('5b644b5a43048277c40834c7', {name: 1, _id: 0}).exec((err, doc) => {
console.log(doc);
//{ name: 'jake2' }
});
//輸出最少字段
model.findById('5b644b5a43048277c40834c7', {lean: true}).exec((err, doc) => {
console.log(doc);
//{ _id: 5b644b5a43048277c40834c7 }
});
-
findOne()
方法
該方法返回查找到的所有實(shí)例的第一個(gè)点寥。
Model.findOne([conditions], [projection], [options], [callback])
model.findOne({age: {$gt: 5}}, (err, doc) => {
console.log(doc);
//{ _id: 5b644b5a43048277c40834cb, name: 'jake6', age: 6, __v: 0 }
});
model.findOne({age: {$gt: 5}}, {_id: 0}).exec((err, doc) => {
console.log(doc);
//{ name: 'jake6', age: 6, __v: 0 }
});
model.findOne({age: {$gt: 5}}, {name: 1}).lean().exec((err, doc)=> {
console.log(doc);
//{ _id: 5b644b5a43048277c40834cb, name: 'jake6' }
});
- 常用的查詢條件如下
$or 或關(guān)系
$nor 或關(guān)系取反
$gt 大于
$gte 大于等于
$lt 小于
$lte 小于等于
$ne 不等于
$in 在多個(gè)值范圍內(nèi)
$nin 不在多個(gè)值范圍內(nèi)
$all 匹配數(shù)組中多個(gè)值
$regex 正則,用于模糊查詢
$size 匹配數(shù)組大小
$maxDistance 范圍查詢来吩,距離(基于LBS)
$mod 取模運(yùn)算
$near 鄰域查詢敢辩,查詢附近的位置(基于LBS)
$exists 字段是否存在
$elemMatch 匹配內(nèi)數(shù)組內(nèi)的元素
$within 范圍查詢(基于LBS)
$box 范圍查詢,矩形范圍(基于LBS)
$center 范圍醒詢弟疆,圓形范圍(基于LBS)
$centerSphere 范圍查詢戚长,球形范圍(基于LBS)
$slice 查詢字段集合中的元素(比如從第幾個(gè)之后,第N到第M個(gè)元素)
-
$where
操作符
如果要進(jìn)行更復(fù)雜的查詢怠苔,需要使用$where
操作符同廉,$where
操作符功能強(qiáng)大而且靈活,它可以使用任意的JavaScript
作為查詢的一部分,包含JavaScript
表達(dá)式的字符串或者JavaScript
函數(shù)迫肖。
(1) 使用字符串
model.find({$where:"this.x == this.y"},(err,docs) => {
console.log(docs);
//[{ _id: 5972ed35e6f98ec60e3dc887,name: 'wang',age: 18,x: 1,y: 1},
//{ _id: 5972ed35e6f98ec60e3dc889, name: 'li', age: 20, x: 2, y: 2 }]
});
model.find({$where:"obj.x == obj.y"}, (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);
});
(2) 使用函數(shù)
model.find({$where:() => {
return obj.x !== obj.y;
}}, (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);
})
model.find({$where:() => {
return this.x !== this.y;
}},(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);
})
八. 更新文檔
更新方法
- 文檔更新可以使用以下幾種方法锅劝。
(1)update()
(2)updateOne()
(3)updateMany()
(4)find() + save()
(5)findOne() + save()
(6)findByIdAndUpdate()
(7)findOneAndUpdate()
-
update()
第一個(gè)參數(shù)conditions
為查詢條件,第二個(gè)參數(shù)doc
為需要修改的數(shù)據(jù)蟆湖,第三個(gè)參數(shù)options
為控制選項(xiàng)故爵,第四個(gè)參數(shù)是回調(diào)函數(shù)。
Model.update(conditions, doc, [options], [callback])
options
有如下選項(xiàng):
safe (boolean): 默認(rèn)為true隅津。安全模式诬垂。
upsert (boolean): 默認(rèn)為false。如果不存在則不創(chuàng)建新記錄伦仍。
multi (boolean): 默認(rèn)為false剥纷。是否更新多個(gè)查詢記錄。
runValidators: 如果值為true呢铆,執(zhí)行Validation驗(yàn)證晦鞋。
setDefaultsOnInsert: 如果upsert選項(xiàng)為true,在新建時(shí)插入文檔定義的默認(rèn)值棺克。
strict (boolean): 以strict模式進(jìn)行更新悠垛。
overwrite (boolean): 默認(rèn)為false。禁用update-only模式娜谊,允許覆蓋記錄确买。
(1) 只更新第一條滿足條件的數(shù)據(jù)
model.update({age: {$gt: 7}}, {age: 10}, (err, row)=> {
//{ n: 1, nModified: 1, ok: 1 }
console.log(row);
});
//使用exec的寫法
model.update({age: {$gt: 7}}, {age: 10}).exec((err, row)=> {
//{ n: 1, nModified: 1, ok: 1 }
console.log(row);
});
(2) 更新所有滿足條件的數(shù)據(jù)
model.update({age: {$gt: 7}}, {age: 10}, {multi: true}).exec((err, row)=> {
//{ n: 2, nModified: 1, ok: 1 }
console.log(row);
});
(3) 如果沒有符合條件的數(shù)據(jù),則什么都不做
model.update({age: 100}, {age: 1000}).exec((err, row)=> {
//{ n: 0, nModified: 0, ok: 1 }
console.log(row);
});
(4) 如果設(shè)置upsert
參數(shù)為true
纱皆,若沒有符合查詢條件的文檔湾趾,mongo
將會(huì)綜合第一第二個(gè)參數(shù)向集合插入一個(gè)新的文檔。
model.update({age: 100}, {name: 'jake100'}, {upsert: true}).exec((err, row)=> {
//{n: 1,
// nModified: 0,
// upserted: [ { index: 0, _id: 5b646510d22cf9feac0bd2f5 } ],
// ok: 1 }
console.log(row);
});
//驗(yàn)證插入文檔
model.find({age: '100'}).exec((err, docs) =>{
console.log(docs)
//[ { _id: 5b646510d22cf9feac0bd2f5,
// age: 100,
// __v: 0,
// name: 'jake100' } ]
});
注意:update()
方法中的回調(diào)函數(shù)不能省略派草,否則數(shù)據(jù)不會(huì)被更新搀缠。如果無需在回調(diào)函數(shù)中做進(jìn)一步操作,則可以使用exec()
簡化代碼近迁。
例如:temp.update({name:/aa/},{age: 0},{upsert:true}).exec();
-
updateOne()
updateOne()
方法只能更新找到的第一條數(shù)據(jù)艺普,即使設(shè)置{multi:true}
也無法同時(shí)更新多個(gè)文檔。
model.updateOne({age: {$gt: 8}}, {name: 'jake80'}).exec((err, res)=> {
//{ n: 1, nModified: 1, ok: 1 }
console.log(res);
});
-
updateMany()
updateMany()
與update()
方法唯一的區(qū)別就是默認(rèn)更新多個(gè)文檔鉴竭,即使設(shè)置{multi:false}
也無法只更新第一個(gè)文檔歧譬。
Model.updateMany(conditions, doc, [options], [callback])
model.updateMany({age: {$gt: 8}}, {name: 'jake80'}).exec((err, res)=> {
//{ n: 3, nModified: 3, ok: 1 }
console.log(res);
});
-
find() + save()
如果需要更新的操作比較復(fù)雜,可以使用find()+save()
方法來處理搏存。
model.find({age: {$gt: 8}}).exec((err, docs)=> {
docs.forEach(doc => {
doc.name = `jake${doc.age}`;
doc.save();
});
console.log(docs);
//[ { _id: 5b644b5a43048277c40834cd, name: 'jake10', age: 10, __v: 0 },
//{ _id: 5b644b5a43048277c40834ce, name: 'jake10', age: 10, __v: 0 },
//{ _id: 5b646510d22cf9feac0bd2f5, age: 100, __v: 0, name: 'jake100' } ]
});
-
findOne() + save()
如果需要更新的操作比較復(fù)雜瑰步,可以使用findOne()
+save()
方法來處理。
model.findOne({age: 10}).exec((err, doc)=> {
doc.name = 'jake9';
doc.age = 9;
doc.save();
console.log(doc);
//{ _id: 5b644b5a43048277c40834cd, name: 'jake9', age: 9, __v: 0 }
});
-
findByIdAndUpdate()
Model.findOneAndUpdate([conditions], [update], [options], [callback])
-
findOneAndUpdate()
Model.findOneAndUpdate([conditions], [update], [options], [callback])
修改器
數(shù)據(jù)準(zhǔn)備工作璧眠,創(chuàng)建集合及文檔數(shù)據(jù)如下:
var mongoose = require('mongoose');
mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
var schema = new mongoose.Schema({
name: String,
age: Number,
array: [Number]
});
var Model = mongoose.model('temp', schema);
for(let i = 1; i < 10; i++) {
new Model({name: `jake${i}`, age: i, array: []
}).save();
}
對象修改器
-
$inc
增減修改器缩焦,只對數(shù)字有效读虏。
找到age
等于1的文檔,修改age
字段值舌界,自減5掘譬。
Model.update({age: 1}, {$inc: {age: -5}}).exec()
-
$set
指定一個(gè)鍵的值
Model.update({name: 'jake1'}, {$set: {age: 2}}).exec()
-
$unset
刪除一個(gè)鍵
Model.update({name: 'jake1'}, {$unset: {age: ''}}).exec()
注意:$unset
操作符只匹配key
,value
可以是任意值呻拌。
數(shù)組修改器
-
$push
數(shù)組尾部插入葱轩。
給匹配文檔的array
鍵對應(yīng)數(shù)組插入數(shù)字1。
Model.update({age: 2}, {$push: {array: 1}}).exec();
-
$addToSet
數(shù)組尾部插入藐握,如果存在則不插入靴拱。
Model.update({age: 2}, {$addToSet: {array: 1}}).exec();
-
$pop
數(shù)組尾部刪除。
傳入1刪除數(shù)組尾元素猾普,傳入-1刪除數(shù)組首元素袜炕。
Model.update({age: 2}, {$pop: {array: 1}}).exec();
-
$pull
刪除數(shù)組指定元素。
Model.update({age: 2}, {$pull: {array: 6}}).exec();
九. 刪除文檔
- 有三種方法用于文檔刪除初家。
(1)remove()
(2)findOneAndRemove()
(3)findByIdAndRemove()
注意:這些方法中的回調(diào)函數(shù)不能省略偎窘,否則數(shù)據(jù)不會(huì)被刪除。當(dāng)然溜在,可以使用exec()
方法來簡寫代碼陌知。 -
remove()
remove
有兩種形式,一種是Model
的remove()
方法掖肋,一種是document
的remove()
方法仆葡。
(1)Model
的remove()
方法
該方法的第一個(gè)參數(shù)conditions
為查詢條件,第二個(gè)參數(shù)為回調(diào)函數(shù)志笼。
model.remove(conditions, [callback])
model.remove({age: {$gte:9}}, (err, res) => {
console.log(res);
//{ n: 3, ok: 1 }
});
//使用exec的寫法
model.remove({age: {$gte:9}}).exec((err, res) => {
console.log(res);
//{ n: 0, ok: 1 }
});
(2) document
的remove()
方法
document.remove([callback])
model.findOne({age: 8}).exec((err, doc) => {
doc.remove((err, doc) => {
console.log(doc);
//{ _id: 5b64fdfa1bfab0852697bc00, name: 'jake8', age: 8, __v: 0 }
});
});
注釋:①model
的remove()
方法回調(diào)可以使用exec()
方法改寫沿盅, document
的remove()
方法不可以。②model
的remove()
方法刪除符合條件的所有document
文檔纫溃,document
的remove()
方法刪除當(dāng)前文檔腰涧。
-
findOneAndRemove()
model
的remove()
會(huì)刪除符合條件的所有數(shù)據(jù),如果只刪除符合條件的第一條數(shù)據(jù)皇耗,則可以使用model
的findOneAndRemove()
方法南窗。
Model.findOneAndRemove(conditions, [options], [callback])
model.findOneAndRemove({age: {$gte: 0}}, (err, doc) => {
console.log(doc);
//{ _id: 5b644b5a43048277c40834c6, name: 'jake1', age: 1, __v: 0 }
});
model.findOneAndRemove({age: {$gte: 0}}).exec((err, doc) => {
console.log(doc);
//{ _id: 5b644b5a43048277c40834c7, name: 'jake2', age: 2, __v: 0 }
});
-
findByIdAndRemove()
Model.findByIdAndRemove(id, [options], [callback])
model.find().exec((err, docs) => {
const docIdArr = docs.map(doc => doc._id);
model.findByIdAndRemove(docIdArr[0]).exec((err, doc) => {
console.log(doc);
//{ _id: 5b644b5a43048277c40834c8, name: 'jake3', age: 3, __v: 0 }
})
});
十. Promise
-
Mongoose
異步操作,例如.save()
方法郎楼,會(huì)返回一個(gè)ES6
標(biāo)準(zhǔn)的promises
。你可以使用類似MyModel.findOne({}).then()
和await MyModel.findOne({}).exec()
的寫法窒悔。
var gnr = new Band({
name: "Guns N' Roses",
members: ['Axl', 'Slash']
});
var promise = gnr.save();
assert.ok(promise instanceof Promise);
promise.then(function (doc) {
assert.equal(doc.name, "Guns N' Roses");
});
-
mongoose queries
查詢操作雖然有then
方法呜袁,但并不是一個(gè)完全的promise
〖蛑椋可以使用exec()
方法將其轉(zhuǎn)化為一個(gè)完全的promise
阶界。
var query = Band.findOne({name: "Guns N' Roses"});
assert.ok(!(query instanceof Promise));
// A query is not a fully-fledged promise, but it does have a `.then()`.
query.then(function (doc) {
// use doc
});
// `.exec()` gives you a fully-fledged promise
var promise = query.exec();
assert.ok(promise instanceof Promise);
promise.then(function (doc) {
// use doc
});
- 可以通過重寫
mongoose.Promise
的方式使用第三方promise
庫虹钮,例如bluebird
。
var query = Band.findOne({name: "Guns N' Roses"});
// Use bluebird
mongoose.Promise = require('bluebird');
assert.equal(query.exec().constructor, require('bluebird'));
十一. 前后鉤子
- 前后鉤子即
pre()
和post()
方法膘融,又稱為中間件芙粱,是在執(zhí)行某些操作時(shí)可以執(zhí)行的函數(shù)。中間件在schema
上指定氧映,類似于靜態(tài)方法或?qū)嵗椒ǖ取?br> 注意:①前后鉤子方法定義在schema
上春畔。② 前后鉤子方法必須在Model
創(chuàng)建之前定義,否則不生效岛都。 - 可以在
model
執(zhí)行下列操作時(shí)律姨,設(shè)置前后鉤子。
init validate save remove count find findOne findOneAndRemove findOneAndUpdate insertMany update
-
pre()
中間件
以find()
方法為例臼疫,在執(zhí)行find()
方法之前择份,執(zhí)行pre()
方法。
mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
var schema = new mongoose.Schema({name: String, age: Number});
schema.pre('find', (next) => {
console.log('pre hook fu1');
next();
});
schema.pre('find', (next) => {
console.log('pre hook fu2');
next();
});
var Model = mongoose.model('temp', schema);
Model.find((err, docs) => {
console.log(docs[0]);
//pre hook fu1
//pre hook fu2
//{ _id: 5b644b5a43048277c40834c9, name: 'jake4', age: 4, __v: 0 }
});
-
post()
中間件
post()
方法并不是在執(zhí)行某些操作后再去執(zhí)行的方法烫堤,而在執(zhí)行某些操作前最后執(zhí)行的方法荣赶,post()
方法里不可以使用next()
。
mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
var schema = new mongoose.Schema({name: String, age: Number});
schema.post('find', (next) => {
console.log('post hook fu1');
});
schema.post('find', (next) => {
console.log('post hook fu2');
});
var Model = mongoose.model('temp', schema);
Model.find((err, docs) => {
console.log(docs[0]);
//post hook fu1
//post hook fu2
//{ _id: 5b644b5a43048277c40834c9, name: 'jake4', age: 4, __v: 0 }
});
十二. 查詢后處理
- 常用的查詢后處理的方法如下所示
-
sort
排序 -
skip
跳過 -
limit
限制 -
select
顯示字段 -
exect
執(zhí)行 -
count
計(jì)數(shù) -
distinct
去重
- 方法示例
mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
var schema = new mongoose.Schema({name: String, age: Number});
var Model = mongoose.model('temp', schema);
for(let i = 1; i <= 15; i++) {
new Model({name: `jake${i}`, age: i}).save();
}
Model.find((err, docs) => {
console.log(docs); //1 - 15
});
//sort 排序
Model.find().sort({'age': -1}).exec((err, docs) => {
console.log(docs); //15 - 1
});
//skip 跳過
Model.find().skip(3).exec((err, docs) => {
console.log(docs); //4 - 15
});
//limit 限制
Model.find().limit(2).exec((err, docs) => {
console.log(docs); //1 - 2
});
//select 限制字段
Model.find().select({name: 1, _id: 0}).exec((err, docs) => {
console.log(docs[0]); //{ name: 'jake2' }
});
//鏈?zhǔn)讲僮?Model.find().sort({'age': -1}).skip(2).limit(1).select({age: 1, _id: 0}).exec((err, docs) => {
console.log(docs); //[ { age: 13 } ]
});
//count顯示文檔數(shù)目
Model.find().count().exec((err, count) => {
console.log(count); //15
});
//distinct 去重
Model.find().distinct('name').exec((err, arr) => {
console.log(arr); //jake1-jake15
});
十三. 文檔驗(yàn)證
- 如果不進(jìn)行文檔驗(yàn)證鸽斟,保存文檔時(shí)拔创,就可以不按照
Schema
設(shè)置的字段進(jìn)行設(shè)置,分為以下幾種情況湾盗。
var mongoose = require('mongoose');
mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
var schema = new mongoose.Schema({name: String, age: Number,x: Number, y: Number});
var Model = mongoose.model('temp', schema);
(1) 缺少字段的文檔也可以保存成功
new Model({age: 10}).save((err, doc) => {
console.log(doc);
//{ _id: 5b65862905b74ea05e211322, age: 10, __v: 0 }
});
(2) 包含未設(shè)置的字段的文檔也可以保存成功伏蚊,未設(shè)置的字段不被保存。
new Model({age: 11, z: 10}).save((err, doc) => {
console.log(doc);
//{ _id: 5b65865ca0864ba06fb02d37, age: 11, __v: 0 }
});
(3) 包含字段類型與設(shè)置不同的文檔可以保存成功格粪,不同字段類型的字段被保存為設(shè)置的字段類型
new Model({age:true,name:10}).save(function(err,doc){
//{ _id: 5b6586b547589ba082d19c3c, age: 1, name: '10', __v: 0 }
console.log(doc);
});
- 通過文檔驗(yàn)證躏吊,就可以避免以上幾種情況發(fā)生。文檔驗(yàn)證在
SchemaType
中定義帐萎,格式如下比伏。
{name: {type:String, validator:value}}
常用驗(yàn)證包括以下幾種:
required
: 數(shù)據(jù)必須填寫
default
: 默認(rèn)值
min
: 最小值(只適用于數(shù)字)
max
: 最大值(只適用于數(shù)字)
match
: 正則匹配(只適用于字符串)
enum
: 枚舉匹配(只適用于字符串)
validate
: 自定義匹配 -
required
文檔驗(yàn)證
將age
設(shè)置為必填字段,如果沒有age
字段疆导,文檔將不被保存赁项,且出現(xiàn)錯(cuò)誤提示。
var schema = new mongoose.Schema({age:{type:Number,required:true}, name: String,x:Number,y:Number});
var Model = mongoose.model('temp', schema);
new Model({name:"abc"}).save((err,doc) => {
//Path `age` is required.
console.log(err.errors['age'].message);
});
-
default
文檔驗(yàn)證
設(shè)置age
字段的默認(rèn)值為18澈段,如果不設(shè)置age
字段悠菜,則會(huì)取默認(rèn)值。
var schema = new mongoose.Schema({ age:{type:Number,default:18}, name:String,x:Number,y:Number});
var Model = mongoose.model('temp', schema);
new Model({name:'a'}).save((err,doc) => {
//{ __v: 0, name: 'a', _id: 59730d2e7a751d81582210c1, age: 18 }
console.log(doc);
});
-
min败富、max
文檔驗(yàn)證
將age
的取值范圍設(shè)置為[0,10]
悔醋。如果age
取值為20
,文檔將不被保存兽叮,且出現(xiàn)錯(cuò)誤提示芬骄。
var schema = new mongoose.Schema({ age:{type:Number,min:0,max:10}, name: String,x:Number,y:Number});
var Model = mongoose.model('temp', schema);
new Model({age:20}).save((err,doc) => {
//Path `age` (20) is more than maximum allowed value (10).
console.log(err.errors['age'].message);
});
-
match
文檔驗(yàn)證
將name
的match
設(shè)置為必須存在'a'
字符猾愿。如果name
不存在'a'
,文檔將不被保存账阻,且出現(xiàn)錯(cuò)誤提示蒂秘。
var schema = new mongoose.Schema({ age:Number, name:{type:String,match:/a/},x:Number,y:Number});
var Model = mongoose.model('temp', schema);
new Model({name:'bbb'}).save((err,doc) => {
//Path `name` is invalid (bbb).
console.log(err.errors['name'].message);
});
-
enum
文檔驗(yàn)證
將name
的枚舉取值設(shè)置為['a','b','c']
,如果name
不在枚舉范圍內(nèi)取值淘太,文檔將不被保存姻僧,且出現(xiàn)錯(cuò)誤提示。
var schema = new mongoose.Schema({ age:Number, name:{type:String,enum:['a','b','c']},x:Number,y:Number});
var Model = mongoose.model('temp', schema);
new Model({name:'bbb'}).save((err,doc) => {
//`bbb` is not a valid enum value for path `name`.
console.log(err.errors['name'].message)
});
-
validate
文檔驗(yàn)證
validate
實(shí)際上是一個(gè)函數(shù)琴儿,函數(shù)的參數(shù)代表當(dāng)前字段的值段化,返回true
表示通過驗(yàn)證,返回false
表示未通過驗(yàn)證造成。利用validate
可以自定義任何條件显熏。
例如,定義名字name
的長度必須在4個(gè)字符以上晒屎。
var schema = new mongoose.Schema({
name:{
type: String,
validate: value =>value.length > 4
},
age: Number,
x: Number,
y: Number
});
var Model = mongoose.model('temp', schema);
new Model({name:'abc'}).save((err, doc) => {
//Validator failed for path `name` with value `abc`
console.log(err.errors['name'].message);
});
十四. population連表操作
-
population
介紹
(1)MongoDB
是文檔型數(shù)據(jù)庫喘蟆,所以它沒有關(guān)系型數(shù)據(jù)庫joins
(數(shù)據(jù)庫的兩張表通過"外鍵"建立連接關(guān)系) 特性。在建立數(shù)據(jù)的關(guān)聯(lián)時(shí)會(huì)比較麻煩鼓鲁。為了解決這個(gè)問題蕴轨,Mongoose
封裝了一個(gè)population
功能。使用population
可以實(shí)現(xiàn)在一個(gè)document
中填充其他collection(s)
的document(s)
骇吭。
(2) 在定義schema
的時(shí)候橙弱,如果設(shè)置某個(gè)field
關(guān)聯(lián)另一個(gè)schema
,那么在獲取document
的時(shí)候就可以使用population
功能通過關(guān)聯(lián)schema
的field
找到關(guān)聯(lián)的另一個(gè)document
燥狰,并且用被關(guān)聯(lián)document
的內(nèi)容替換掉原來關(guān)聯(lián)字段(field
)的內(nèi)容棘脐。 - 連表關(guān)系場景
場景:用戶user
可以寫文章post
,并且對文章post
進(jìn)行評論comment
龙致。
分析:一個(gè)用戶user
可以寫多篇文章post
蛀缝。一篇文章post
只能有一個(gè)作者user
,但可以有多條評論comment
目代。一條評論comment
只屬于一篇文章post
屈梁,且只屬于一個(gè)用戶user
。
示例:用戶A
寫了文章A
榛了、評論A
在讶,用戶B
寫了文章B
、評論B
霜大,用戶C
寫了文章C
真朗、評論C
;用戶A
在文章B
上添加了評論A
僧诚,用戶B
在文章C
上添加了評論B
遮婶,用戶C
在文章A
上添加了評論C
。 - 連表關(guān)系示例代碼
(1) 創(chuàng)建用戶湖笨、文章旗扑、評論三個(gè)schema
和Model
var mongoose = require('mongoose');
mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
var Schema = mongoose.Schema;
//創(chuàng)建用戶scheme以及model
var userSchema = new Schema({
name : String,
posts : [{ type: Schema.Types.ObjectId, ref: 'post' }]
});
var UserModel = mongoose.model('user', userSchema);
//創(chuàng)建文章scheme以及model
var postSchema = new Schema({
name : String,
poster : { type: Schema.Types.ObjectId, ref: 'user' },
comments : [{ type: Schema.Types.ObjectId, ref: 'comment' }],
});
var PostModel = mongoose.model('post', postSchema);
//創(chuàng)建評論scheme以及model
var commentSchema = new Schema({
name : String,
post : { type: Schema.Types.ObjectId, ref: "post" },
commenter : { type: Schema.Types.ObjectId, ref: 'user' },
});
var CommentModel = mongoose.model('comment', commentSchema);
- 創(chuàng)建了三個(gè)
Model
:UserModel
,PostModel
慈省,CommentModel
臀防。 -
UserModel
的屬性posts
對應(yīng)是一個(gè)ObjectId
的數(shù)組,ref
表示關(guān)聯(lián)PostModel
边败。 -
PostModel
的屬性poster
和comments
分別關(guān)聯(lián)UserModel
和CommentModel
袱衷。 -
CommentModel
的屬性post
和commenter
分別關(guān)聯(lián)PostModel
和UserModel
。
注意:①ref
指向mongoose.model(name, schema);
方法的name
參數(shù)笑窜,而不是方法返回值model
致燥。②userSchema
中應(yīng)該保存而未保存comments
數(shù)組。
(2) 創(chuàng)建entity
實(shí)例排截、建立關(guān)系并保存數(shù)據(jù)
//創(chuàng)建三個(gè)用戶userA嫌蚤、userB、userC
var userA = new UserModel({name: 'userA'});
var userB = new UserModel({name: 'userB'});
var userC = new UserModel({name: 'userC'});
//創(chuàng)建三篇文章postA断傲、postB脱吱、postC
var postA = new PostModel({name: 'postA'});
var postB = new PostModel({name: 'postB'});
var postC = new PostModel({name: 'postC'});
//創(chuàng)建三個(gè)評論commentA、commentB认罩、commentC
var commentA = new CommentModel({name: 'commentA'});
var commentB = new CommentModel({name: 'commentB'});
var commentC = new CommentModel({name: 'commentC'});
//建立用戶與文章的關(guān)系
userA.posts.push(postA._id);
//moongoose封裝的語法糖箱蝠,與userA.posts.push(postA)寫法含義相同
userB.posts.push(postB);
userC.posts.push(postC);
//建立文章與用戶、評論的關(guān)系
postA.poster = userA;
postB.poster = userB;
postC.poster = userC;
postA.comments.push(commentC);
postB.comments.push(commentA);
postC.comments.push(commentB);
//建立評論與用戶垦垂、文章的關(guān)系
commentA.post = postB;
commentB.post = postC;
commentC.post = postA;
commentA.commenter = userA;
commentB.commenter = userB;
commentC.commenter = userC;
//保存數(shù)據(jù)
userA.save();
userB.save();
userC.save();
postA.save();
postB.save();
postC.save();
commentA.save();
commentB.save();
commentC.save();
-
population
連表操作
(1)query.populate
語法:query.populate(path, [select], [model], [match], [options])
path
:String | Object
宦搬;指定要填充的關(guān)聯(lián)字段。
select
:Object | String
乔外;指定填充document
中的哪些字段床三。
model
:Model
;指定關(guān)聯(lián)字段的model
杨幼,若未指定則使用Schema
的ref
撇簿。
match
:Object
;指定附加的查詢條件差购。
options
:Object
四瘫;指定附加的其他查詢選項(xiàng),如排序以及條數(shù)限制等欲逃。
UserModel.find().skip(1).limit(1)
.populate('posts', 'name')
.exec((err, docs) => {
console.log(docs[0].posts);
//[{"_id":"5b6655ac4f1303b2933a7dc0","name":"postB"}]
});
UserModel.findOne({name: 'userC'})
.populate({
path: 'posts',
select: { name: 1, _id: 0}
})
.exec((err, doc) => {
console.log(doc.posts); //[{"name":"postC"}]
});
PostModel.findOne({name: 'postA'})
.populate('poster comments', 'name -_id')
.exec((err, doc)=> {
console.log(doc.poster); //{ name: 'userA' }
console.log(doc.comments); //[{"name":"commentC"}]
});
PostModel.findOne({name: 'postC'})
.populate([
{path: 'poster', select: 'name'},
{path: 'comments', select: {_id: 0}}
])
.exec((err, doc) => {
console.log(doc.poster);
//{ _id: 5b6655ac4f1303b2933a7dbe, name: 'userC' }
console.log(doc.comments);
//[{"name":"commentB","post":"5b6655ac4f1303b2933a7dc1","commenter":"5b6655ac4f1303b2933a7dbd","__v":0}]
});
(2) Model.populate
語法:Model.populate(docs, options, [cb(err,doc)])
CommentModel.findOne((err, doc) => {
CommentModel.populate(doc, {path: 'post commenter', select: 'name'}, (err, doc) => {
console.log(doc.post);
//{ _id: 5b6655ac4f1303b2933a7dc0, name: 'postB' }
console.log(doc.commenter);
//{ _id: 5b6655ac4f1303b2933a7dbc, name: 'userA' }
})
});
(3) document.populate
語法:Document.populate([path], [callback])
CommentModel.findOne((err, doc) => {
doc.populate({path: 'post commenter', select: 'name'}, (err, doc) => {
console.log(doc.post);
//{ _id: 5b6655ac4f1303b2933a7dc0, name: 'postB' }
console.log(doc.commenter);
//{ _id: 5b6655ac4f1303b2933a7dbc, name: 'userA' }
})
});
十五. 參考資料
Mongoose官網(wǎng)
Mongoose Promise語法
Mongoose基礎(chǔ)入門
Mongoose 之 Population 使用
Mongoose 使用之 Population