http://mongoosejs.com/docs/api.html#index-js
mongoose是nodejs環境下操作mongodb的模塊封裝,使用mongoose之后,實際上只需要在mongodb中創建好數據庫與用戶,集合的定義、創建、操作等直接使用mongoose即可。
- 一、連接
- 二、重要概念
- 三、基本操作
- 1、Schema
- 2、Model
- 3、實例化document
- 4、保存數據
- 5、文檔查詢
- 6、文檔更新
- 7、文檔刪除
- 8、自定義方法
- 9、虛擬屬性
- 10、前置與后置鉤子
一、連接
let mongoose = require('mongoose');//連接mongodb
//非auth模式
//mongoose.connect('mongodb://localhost:27017/mall');
//auth模式
/**
mongodb 為協議
第一個mall: 連接數據庫的用戶名
123456: 用戶的密碼
localhost: mongodb地址
27017: mongodb端口號
第二個mall: 數據庫名字
**/
mongoose.connect('mongodb://mall:123456@localhost:27017/mall');//連接失敗
mongoose.connection.on('connected',() => {console.log('mongodb connected!');
});//連接成功
mongoose.connection.on('error',(err) => {console.log('mongodb connect fail:'+err);
});//連接斷開
mongoose.connection.on('disconnected',() => {console.log('mongodb connect disconnected!');
});// connection的事件列表可查看:http://mongoosejs.com/docs/api.html#connection_Connection
// 或 ./node_modules/mongoose/lib/connection.js#Connection()// 關閉的兩種方式
// mongoose.connection.close(); 等同于 db.close();
// mongoose.disconnect();
二、重要概念
Mongooose中,有三個比較重要的概念,Schema、Model、Document。Schema生成Model,Model實例化成為Document。
Schema用于定義數據庫的結構。類似創建表時的數據定義(不僅僅可以定義文檔的結構和屬性,還可以定義文檔的實例方法、靜態模型方法、復合索引等),每個Schema會映射到mongodb中的一個collection,Schema不具備操作數據庫的能力。
Model是由Schema編譯而成的構造器,具有抽象屬性和行為,可以對數據庫進行增刪查改。Model的每一個實例(instance)就是一個文檔document。
Document是new Model后創建的實例,它的操作會影響數據庫。
三、基本操作
1、Schema
Schema是對mongodb中某個集合的結構描述,它定義一個集合中應該包含哪些數據項,每個數據項的數據類型與檢查約束。可以想象成一個抽象類,只有定義,沒有實現。就是規定好某個集合的框架應該是怎么樣的。也可以理解是mysql中的表結構。
可以為數據項指定的數據類型有8種:
String 字符串
Number 數字
Date 日期
Buffer 二進制
Boolean 布爾值
Mixed 混合類型
ObjectId 對象ID
Array 數組
創建Schema
const mongoose = require('mongoose')
//獲取Schema
const Schema = mongoose.Schema;//聲明一個Schema實例,實際就是創建一個集合的結構框架
let animalSchema = new Schema({title: String, //類型可以是首字母大寫age: 'number', //類型也可以寫成字符串全小寫food: [{ //數組name: String}]
});//如果需要為animalSchema增加屬性,可以使用add
animalSchema.add({sex: {type: String,enum: ['male', 'female'] //規定,字段值為枚舉約束,只能填male/female}
});
創建Schema時可以有一系列的約束條件,類似mysql中字段的非空、唯一等約束。
基本語法是:
{name: {type:String, validator:value}}
常用的約束有:
required: 數據必須填寫
default: 默認值
min: 最小值(只適用于數字)
max: 最大值(只適用于數字)
match: 正則匹配(只適用于字符串)
enum: 枚舉匹配(只適用于字符串)
validate: 自定義匹配
其中validate是自定義約束
例如要自定義檢查長度
// 自定義長度檢查
let lengthValidate = (param) => {return param.length <= 10
};//如果需要為animalSchema增加屬性,可以使用add
animalSchema.add({sex: {type: String,enum: ['male', 'female'], //規定,字段值為枚舉約束,只能填male/femalevalidate: lengthValidate}
});
2、Model
model是根據Schema定義的結構框架生成一個映射mongodb的數據模型,可以把它理解是一個類,是根據Schema創建的一個類,他的數據結構是依據Schema生成的,這個類用來生成document實例。
//通過schema構建model,第一個參數是給model起個名字,第二個參數是構造這個model所依據的schema
let animalModel = mongoose.model('animalModel', animalSchema);
3、實例化document
document是相當于是mongodb中的每一條數據了,它是通過new一個model得到的。
// 通過實例化model得到document,document的數據項需根據schema定義的結構來寫// 通過實例化model得到document,document的數據項需根據schema定義的結構來寫let cat = new animalModel({title: '貓咪',food: [{name: '魚'},{name: '火腿'}],sex: 'male'});
到這一步,mongodb中還沒有animalMode集合以及數據。
4、保存數據
- 1、save()
在mongoose中保存數據,實際就是保存某個document,document通過model實例化來創建。document的save()方法可以將document映射保存到mongodb中,save()方法中可以傳遞一個回調函數作為參數。因為是在document上調用,自然一次只保存一條文檔。
方法原型:
save(function (err, document) {}) //回調函數中第二個參數是使用保存的數據形成的對象
保存cat:
//保存document,映射成為一條記錄
cat.save((err, catObj) => {if (err) {console.log('保存失敗');console.info(err);} else {console.info(catObj);}
});
保存后打印:
{ title: '貓咪',food: [ { name: '魚', _id: 5a96555e95f678530054f918 },{ name: '火腿', _id: 5a96555e95f678530054f917 } ],sex: 'male',_id: 5a96555e95f678530054f919,__v: 0 }
保存后這個回調中的第二個參數就被填充好值,其中的主鍵_id也在其中,__v是mongoose用來管理數據版本號的標識,自己不用動。
這時看mongodb中已有集合,集合中已經有一條數據(文檔)了。
注意:
1、從上圖可以看出來,集合的名字是根據mongoose.model('animalModel', animalSchema)
中第一個參數來的,變成小寫且復數形式,如果結尾是數字則不變。比如這個地方如果第一個參數給的是’animal’,則生成的集合名稱就應該是’animals’。
2、在mongoose中操作mongodb,因為有了schema、model、ducument的概念,就不是像mysql一樣,事先創建好表結構,而是根據數據結構設計,在代碼中聲明好schema,再生成model、創建document,進行數據操作。
- 2、create()
和save()不一樣的是,create()不是在document上調用的,而是在model上調用的,并且可以一次保存多條文檔。
接著用上面的animalModel,這次不去new它了,也就是不去操作document了,直接用mongoose.model出來的model。同時保存兩條狗。
let dog1 = {title: '1號狗狗',food: [{name: '骨頭'},{name: '肉肉'},],sex: 'male'};let dog2 = {title: '2號狗狗',food: [{name: '骨頭'},{name: '肉肉'},],sex: 'male'};animalModel.create(dog1, dog2, (err, rsObj1, rsObj2) => {if (err) {console.log('保存失敗');console.info(err);} else {console.log('保存成功');console.info(rsObj1);console.info(rsObj2);}});
打印的結果:
保存成功
{ title: '1號狗狗',food: [ { name: '骨頭', _id: 5a96607b5ce650532494f010 },{ name: '肉肉', _id: 5a96607b5ce650532494f00f } ],sex: 'male',_id: 5a96607b5ce650532494f011,__v: 0 }
{ title: '2號狗狗',food: [ { name: '骨頭', _id: 5a96607b5ce650532494f013 },{ name: '肉肉', _id: 5a96607b5ce650532494f012 } ],sex: 'male',_id: 5a96607b5ce650532494f014,__v: 0 }
再看看mongodb中:
注意:這里也驗證了一個問題,就是如果model對應的集合已經存在于mongodb中了,則會直接往這個集合中添加文檔,如果還沒有,則會創建集合。所以在上面保存貓咪的時候,同時創建了集合與添加貓咪文檔,而這里的狗狗,直接添加文檔。
- 3、insertMany()
insertMany()也是model的方法,一次插入多條數據,注意回調中的返回值是所有結果對象的數組。
let monkey1 = {title: '1號猴猴',food: [{name: '香蕉'},{name: '桃'},],sex: 'male'};let monkey2 = {title: '2號猴猴',food: [{name: '香蕉'},{name: '桃'},],sex: 'male'};animalModel.insertMany([monkey1, monkey2], (err, rsArr) => {if (err) {console.log('保存失敗');console.info(err);} else {console.log('保存成功');console.info(rsArr);}});
打印結果:
保存成功
[ { title: '1號猴猴',food: [ [Object], [Object] ],sex: 'male',_id: 5a967c3f44d6d753b6cd6354,__v: 0 },{ title: '2號猴猴',food: [ [Object], [Object] ],sex: 'male',_id: 5a967c3f44d6d753b6cd6357,__v: 0 } ]
5、文檔查詢
mongodb中的數據查詢就是查文檔。
- 1、find()
find()是用在model上的,這一點很容易理解,既然是查詢數據,那么當前肯定是不明確要得到的文檔結果是怎樣的,方法也就不會在document上。
find()方法在官方文檔中的描述是:
Model.find()Parameters [callback] ?Function?
Returns:?Query?Finds documents
The conditions are cast to their respective SchemaTypes before the command is sent.Example:
// named john and at least 18
MyModel.find({ name: 'john', age: { $gte: 18 }});// executes immediately, passing results to callback
MyModel.find({ name: 'john', age: { $gte: 18 }}, function (err, docs) {});// name LIKE john and only selecting the "name" and "friends" fields, executing immediately
MyModel.find({ name: /john/i }, 'name friends', function (err, docs) { })// passing options
MyModel.find({ name: /john/i }, null, { skip: 10 })// passing options and executing immediately
MyModel.find({ name: /john/i }, null, { skip: 10 }, function (err, docs) {});// executing a query explicitly
var query = MyModel.find({ name: /john/i }, null, { skip: 10 })
query.exec(function (err, docs) {});// using the promise returned from executing a query
var query = MyModel.find({ name: /john/i }, null, { skip: 10 });
var promise = query.exec();
promise.addBack(function (err, docs) {});
官方示例find的用法非常清晰了,find方法最簡的參數是只給一個回調函數,表示查詢所有結果,相當于是sql中的select * from 表名。
find()方法返回值是query對象,query對象有exec和一系列方法,exec()可以執行準備好的find()查詢,回調得到結果。
回到animal示例中:
查詢animalmodels集合中所有的文檔:
//方式一:
animalModel.find((err, rs) => {if (err) {console.log('查詢失敗');console.info(err);} else {console.log('查詢成功');console.info(rs);}});//方式二:let animalQuery = animalModel.find();animalQuery.exec((err, rs) => {if (err) {console.log('查詢失敗');console.info(err);} else {console.log('查詢成功');console.info(rs);}});//兩種方式的結果是一樣的,返回的rs是一個包含集合中所有文檔的數組
find()的完整參數列表是:
Model.find([查詢條件], [返回字段列表], [設置選項], [回調函數]);
對于第一個參數,比如age: { $gte: 18 },表示要查詢年齡大于等于18的文檔。常用的條件操作符有:
$lt、$lte、$gt、$gte、$in、$nin、$eq、$ne、$or(model.find({$or:[{ color: 'red' }, { status: 'emergency' }]}))、$and、$not、$nor、$exits(值是否存在,model.find({name:{$exits:true}}))、$all(通常用來匹配數組里面的鍵值,匹配多個值(同時具有) $all:[“apple”,“banana”,“peach”]})、$size(用來查詢數組的長度值 model.find({name:{$size:3}}); 匹配name的數組長度為3)
有一個特殊的操作符$where
,可以靈活的設置查詢條件,$where
后可以直接寫任何的js作為查詢條件,但實際上完全可以使用model.where()方法來代替,where()會更加方便。
{$where:"this.x == this.y"}
{$where:function(){return obj.x !== obj.y;
}}
這些操作符在find的第一個參數中使用,query中也有一一對一個的方法。
對于第二個參數,可以設置要返回的字段列表,{name:1,_id:0}表示返回name字段,不返回_id字段。
對于第三個參數,官方示例中用到了一個skip,還有limit、sort。
除了find()外,還有findById()、findOne(),與find的用法都是一樣的。只不過findById的第一個參數是準確的id值。這兩個方法都只返回一條文檔。
- 2、where()
官網上這么介紹的
Model.where()
Parameters [val] ?Object? optional valueReturns:?Query?
Creates a Query, applies the passed conditions, and returns the Query.
官方示例:
User.find({age: {$gte: 21, $lte: 65}}, callback);
等同于
User.where('age').gte(21).lte(65).exec(callback);
因為where返回的是query對象,所以可以鏈式調用:
User
.where('age').gte(21).lte(65)
.where('name', /^b/i)
... etc
所以在實際的使用過程中,用query對象來進行查詢是比較方便的,用find()的返回值query鏈式調用其他方法,或者用where()的返回值query鏈式調用其他方法,最后用exec(callback)得到結果,這樣比較清晰。query有一系列的方法,異步官方文檔。
- 3、query常用方法
sort 排序
skip 跳過
limit 限制
select 顯示字段
exect 執行
count 計數
distinct 去重
關于分頁與排序,query的三個方法:
query.limit(20); //只返回前20個內容
query.skip(2); //跳過多少條,實際就是開始的索引
query.sort({name:1,age:-1}); //1升序,2降序
6、文檔更新
- 1、update()
Model.update(conditions, doc, [options], [callback])
第一個參數conditions為查詢條件,第二個參數doc為需要修改的數據,第三個參數options為控制選項,第四個參數是回調函數。
options有以下選項:
safe (boolean): 默認為true。安全模式。upsert (boolean): 默認為false。如果不存在則創建新記錄。multi (boolean): 默認為false。是否更新多個查詢記錄。runValidators: 如果值為true,執行Validation驗證。setDefaultsOnInsert: 如果upsert選項為true,在新建時插入文檔定義的默認值。strict (boolean): 以strict模式進行更新。overwrite (boolean): 默認為false。禁用update-only模式,允許覆蓋記錄。
把名字叫貓咪的文檔的名字更新成貓貓:
animalModel.update({title :{$eq: '貓咪'}}, {title: '貓貓'}, (err, rs) => {if (err) {console.log('更新失敗');console.info(err);} else {console.log('更新成功');console.info(rs);}});
如果不設置multi的話,即使有多條符合條件的文檔,也只更新一條。
如果設置options里的upsert參數為true,若沒有符合查詢條件的文檔,mongo將會綜合第一第二個參數向集合插入一個新的文檔。
2、updateMany()
updateMany()與update()的區別是更新多個文檔,即使設置{multi:false}也更新多個。3、updateOne()
updateOne()只更新找到的第一條數據,即使設置{multi:true}只更新一個。4、數組更新
update第二個參數,實際可以寫成{$set: {title: '貓貓'}}
,$set
操作符表示設值,如果要更新的是數組,可以有以下操作符:
$addToSet // 當且僅當待添加到數組的元素是當前數組所沒有時,才會去添加
$pop // 當指定數組的修飾符值為-1時,刪除該數組的第1個元素;當值為1時,刪除最后一個元素
$pull // 刪除指定查詢條件下的數組元素
$pullAll // 刪除數組中符合給定的集合的值的所有元素,與$pull的區別于目標刪除元素是被逐一列舉出來的
$push // 將元素添加到指定的數組中
給貓添加一個食物:
animalModel.update({title :{$eq: '喵星人'}},{$addToSet:{food: {name: '蛋黃派'}}},(err,rs) =>{if (err) {console.info("保存失敗");console.info(err);} else {console.info("保存成功");console.info(rs);}});
結果:
保存成功
{ n: 1, nModified: 1, ok: 1 }
- 5、其他
關于其他更新方法還有findOneAndUpdate()、findByIdAndUpdate()。
通過查詢+保存操作也可以實現更新,查詢的結果實際是一個document對象,調用save方法,猜想是通過_id與_v來進行的比對,將直接更新本條記錄。
animalModel.where('title').eq('貓貓').exec((err, rs) => {if (err) {console.info(err);} else {console.info(rs);rs.forEach(function(item,index,array){item.title = "喵星人";item.save((err,rs) =>{if (err) {console.info("保存失敗");console.info(err);} else {console.info("保存成功");console.info(rs);}});});}});
7、文檔刪除
- 1、remove()
remove方法存在兩個位置,一個是model上,一個是ducument上,model上就是根據條件去刪除集合內某些文檔,文檔上就是刪除自己。
model.remove(conditions, [callback])
document.remove([callback])
示例:
//model刪除animalModel.remove({title :{$eq: '喵星人'}},(err,rs) =>{if (err) {console.info("刪除失敗");console.info(err);} else {console.info("刪除成功");console.info(rs);}});//document刪除animalModel.where('title').eq('2號猴猴').exec((err, rs) => {if (err) {console.info(err);} else {console.info(rs);rs.forEach(function(item,index,array){item.remove((err,rs) =>{if (err) {console.info("刪除失敗");console.info(err);} else {console.info("刪除成功");console.info(rs);}});});}});
- 2、findOneAndRemove()
model的remove()會刪除符合條件的所有數據,如果只刪除符合條件的第一條數據,可以使用model的findOneAndRemove()方法
Model.findOneAndRemove(conditions, [options], [callback])
- 3、findByIdAndRemove()
通過id刪除文檔
Model.findByIdAndRemove(id, [options], [callback])
- 4、其他
所有刪除方法的回調函數都不能省略,否則不成功。要么寫在方法的回調函數參數中,如果不寫回調函數參數,則可以在最后調用.exec()。
8、自定義方法
mongoose中可以利用已有的api,自定義自己的方法來處理特定的業務需要。
- 1、實例方法
通過new model得到的document實例有很多增刪改查的方法,如上面的save()方法用來保存文檔,我們可以給實例增加自定義方法來實現特定的業務邏輯,增加之后就可以像調用save一樣來調用自己的方法,給document增加自定義方法是通過擴展Schema的methods屬性來完成的。
為animalSchema擴展document實例方法:
//為document實例添加eat方法,返回所有喜歡吃的食物
animalSchema.methods.eat = function () {let foodList = this.food;let foodStr = '';foodList.forEach(function (item, index, arr) {if (index == 0) {foodStr += item.name;} else {foodStr += ","+item.name;}});return foodStr;
};
document實例調用:
animalModel.findById('5a96607b5ce650532494f011').exec((err, rs) => {if (err) {console.info("查詢失敗");console.info(err);} else {console.info("查詢成功");console.info(rs);let eatStr = rs.eat();console.info(eatStr); //骨頭,肉肉}});
- 2、靜態方法
靜態方法,是不需要實例化得到document,而是直接在model上調用的方法。
所以,實例方法,是操作文檔(mongodb中的某一條記錄)數據用的,靜態方法是操作集合用的。
為animalSchema添加靜態方法:
//為model添加findDogs方法
animalSchema.statics.findDogs = function (backfun){this.find({title: /狗/}).exec(backfun);
};
model調用:
animalModel.findDogs((err, rs) => {if (err) {console.info("查詢失敗");console.info(err);} else {console.info("查詢成功");console.info(rs); //打印出兩條狗的document}});
-3、查詢方法
擴展schema對象的query屬性,給model添加查詢方法,主要為了擴展query的個性化方法需要。
//為query添加findMonkey方法
animalSchema.query.findMonkey = function (backfun){this.find({title: /猴/}).exec(backfun);
};
query調用:
let rsQuery = animalModel.find();rsQuery.findMonkey((err, rs) => {if (err) {console.info("查詢失敗");console.info(err);} else {console.info("查詢成功");console.info(rs); //打印出一只猴子的document}});
9、虛擬屬性
schema上除了可以設置方法以外,還可以設置虛擬屬性,就像vue的getter一樣,實際數據庫中沒有這個屬性,但可以通過虛擬屬性機制自定義一個經過處理的值,雖然用方法同樣能實現,但虛擬屬性效率更高。
//添加虛擬屬性nameAndSex
animalSchema.virtual('info').get(function () {return this.title + "," +this.sex;
});
調用:
animalModel.find({title: /狗/}).exec((err, rs) => {if (err) {console.info("查詢失敗");console.info(err);} else {console.info("查詢成功");rs.forEach( function (item, index, arr) {console.info(item.info);});}});
打印結果:
1號狗狗,male
2號狗狗,male
10、前置與后置鉤子
通過schema可以為指定的某些方法添加前置鉤子函數與后置鉤子函數。前置鉤子函數在指定方法開始前調用,后置鉤子函數不是在指定方法結束后操作,二是在指定方法開始前的最后一步操作。
前置鉤子是pre()
后置鉤子是post()
//為find方法指定前置操作1
animalSchema.pre('find', function (next) {console.info('前置操作1');next();
});//為find方法指定前置操作2
animalSchema.pre('find', function (next) {console.info('前置操作2');next();
});//為find方法指定后置操作1
animalSchema.post('find', function () {console.info('后置操作1');
});//為find方法指定后置操作2
animalSchema.post('find', function () {console.info('后置操作2');
});
調用find()方法
animalModel.find({title: /狗/}).exec((err, rs) => {if (err) {console.info("查詢失敗");console.info(err);} else {console.info("查詢成功");console.info(rs);}});
得到打印結果:
前置操作1
前置操作2
后置操作1
后置操作2
查詢成功
[ { food: [ [Object], [Object] ],_id: 5a96607b5ce650532494f011,title: '1號狗狗',sex: 'male',__v: 0 },{ food: [ [Object], [Object] ],_id: 5a96607b5ce650532494f014,title: '2號狗狗',sex: 'male',__v: 0 } ]
注意,前置鉤子中有一個next函數,如果方法內不調用next(),被設置前置鉤子的方法(比如find())執行到鉤子時不會繼續向下執行。
可以添加前后鉤子的方法有:
init
validate
save
remove
count
find
findOne
findOneAndRemove
findOneAndUpdate
insertMany
update
以上是mongoose的常用基本操作,還有索引、聚合等等操作需要積累補充。