mongoose操作mongodb
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種:
創建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中字段的非空、唯一等約束。
基本語法是:
常用的約束有:
required: 數據必須填寫 default: 默認值 min: 最小值(只適用于數字) max: 最大值(只適用于數字) match: 正則匹配(只適用于字符串) enum: 枚舉匹配(只適用于字符串) validate: 自定義匹配其中validate是自定義約束
例如要自定義檢查長度
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上調用,自然一次只保存一條文檔。
方法原型:
保存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。同時保存兩條狗。
打印的結果:
保存成功 { 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的方法,一次插入多條數據,注意回調中的返回值是所有結果對象的數組。
打印結果:
保存成功 [ { 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()方法在官方文檔中的描述是:
官方示例find的用法非常清晰了,find方法最簡的參數是只給一個回調函數,表示查詢所有結果,相當于是sql中的select * from 表名。
find()方法返回值是query對象,query對象有exec和一系列方法,exec()可以執行準備好的find()查詢,回調得到結果。
回到animal示例中:
查詢animalmodels集合中所有的文檔:
find()的完整參數列表是:
Model.find([查詢條件], [返回字段列表], [設置選項], [回調函數]);
對于第一個參數,比如age: { $gte: 18 },表示要查詢年齡大于等于18的文檔。常用的條件操作符有:
有一個特殊的操作符$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()
官網上這么介紹的
官方示例:
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常用方法
關于分頁與排序,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有以下選項:
把名字叫貓咪的文檔的名字更新成貓貓:
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操作符表示設值,如果要更新的是數組,可以有以下操作符:
給貓添加一個食物:
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來進行的比對,將直接更新本條記錄。
7、文檔刪除
- 1、remove()
remove方法存在兩個位置,一個是model上,一個是ducument上,model上就是根據條件去刪除集合內某些文檔,文檔上就是刪除自己。
示例:
//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()方法
- 3、findByIdAndRemove()
通過id刪除文檔
- 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調用:
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號狗狗,male10、前置與后置鉤子
通過schema可以為指定的某些方法添加前置鉤子函數與后置鉤子函數。前置鉤子函數在指定方法開始前調用,后置鉤子函數不是在指定方法結束后操作,二是在指定方法開始前的最后一步操作。
前置鉤子是pre()
后置鉤子是post()
調用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的常用基本操作,還有索引、聚合等等操作需要積累補充。
總結
以上是生活随笔為你收集整理的mongoose操作mongodb的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nodejs的启动方式
- 下一篇: vue滚动加载插件vue-infinit