一篇文章帶你入門Mongoose

走在前端的大道上html

Mongoose是在node.js環境下對mongodb進行便捷操做的對象模型工具前端

所以,要使用mongoose,則必須安裝node.js環境以及mongodb數據庫。mongoose使mongodb操做更簡單便捷。能夠在 github 中得到其源碼,也能夠在這裏查看 api 文檔,英文的。node

安裝

  
安裝nodejs和mongodb以後 ,使用npm來安裝mongoosegit

npm install mongoose --save

安裝成功後,就能夠經過 require('mongoose') 來使用github


connect

connect 用於建立數據庫鏈接

mongoose.connect(url(s), [options], [callback])
//url(s):數據庫地址,能夠是多個,以`,`隔開
//options:可選,配置參數
//callback:可選,回調
mongoose.connect('mongodb://數據庫地址(包括端口號)/數據庫名稱')

指定用戶鏈接

mongoose.connect('mongodb://用戶名:密碼@127.0.0.1:27017/數據庫名稱')

鏈接多個數據庫

若是你的app中要鏈接多個數據庫,只須要設置多個url以,隔開,同時設置mongos爲truemongodb

mongoose.connect('urlA,urlB,...', {
   mongos : true 
})

回調參數

mongoose.connect(url, options, function(error) {

});

執行下列代碼後,控制檯輸出「鏈接成功」數據庫

var mongoose = require('mongoose');
mongoose.connect("mongodb://localhost/test", function(err) {
    if(err){
        console.log('鏈接失敗');
    }else{
        console.log('鏈接成功');
    }
});

 若是開啓鑑權控制,以用戶名"u1",密碼"123456"登陸'db1'數據庫。執行代碼後,控制檯輸出「鏈接成功」npm

var mongoose = require('mongoose');
mongoose.connect("mongodb://u1:123456@localhost/db1", function(err) {
    if(err){
        console.log('鏈接失敗');
    }else{
        console.log('鏈接成功');
    }
});

簡單實驗segmentfault

建立一個db.jsapi

var mongoose = require('mongoose'),
    DB_URL = 'mongodb://localhost:27017/mongoosesample';

/**
 * 鏈接
 */
mongoose.connect(DB_URL);

/**
  * 鏈接成功
  */
mongoose.connection.on('connected', function () {    
    console.log('Mongoose connection open to ' + DB_URL);  
});    

/**
 * 鏈接異常
 */
mongoose.connection.on('error',function (err) {    
    console.log('Mongoose connection error: ' + err);  
});    
 
/**
 * 鏈接斷開
 */
mongoose.connection.on('disconnected', function () {    
    console.log('Mongoose connection disconnected');  
});

  調用node db.js執行就會看到輸出以下圖

clipboard.png


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);

clipboard.png


Schema

Schema主要用於定義MongoDB中集合Collection裏文檔document的結構,能夠理解爲mongoose對錶結構的定義(不只僅能夠定義文檔的結構和屬性,還能夠定義文檔的實例方法、靜態模型方法、複合索引等),每一個schema會映射到mongodb中的一個collection,schema不具有操做數據庫的能力

 定義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' });

Model

 Model是由Schema編譯而成的假想(fancy)構造器,具備抽象屬性和行爲。Model的每個實例(instance)就是一個document,document能夠保存到數據庫和對數據庫進行操做。簡單說就是model是由schema生成的模型,能夠對數據庫的操做。

  使用model()方法,將Schema編譯爲Model。model()方法的第一個參數是模型名稱

mongoose.model(`文檔名稱`, Schema)

  注意 必定要將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"}的文檔

clipboard.png

自定義方法
【實例方法】

  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添加方法

【Methods 和 Statics 的區別】

statics是給model添加方法,methods是給實例(instance)添加方法。methods和statics的區別

//module.exports = mongoose.model(`Article`, ArticleSchema )
//將article的model保存爲文件 article.js

const Article = require('../models/article')

// statics
Article.staticFunc ()

//methods
const article = new Article(arguments)
article.methodFunc()

【查詢方法】

  經過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中存在以下數據

clipboard.png

 如今,使用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函數

clipboard.png

使用字符串

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中現有數據以下

clipboard.png

如今使用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歲。而第三個數據沒有發生變化

clipboard.png

  若是要同時更新多個記錄,須要設置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);
        })

    }           
});

clipboard.png

若是設置的查找條件,數據庫裏的數據並不知足,默認什麼事都不發生

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);
})

clipboard.png

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);
})

clipboard.png

 [注意]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);
});

clipboard.png

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);
});

clipboard.png

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])

clipboard.png

  刪除數據庫中名稱包括'30'的數據

temp.remove({name:/30/},function(err){})

clipboard.png

 [注意]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);
        })
    })
})

clipboard.png

findOneAndRemove()

model的remove()會刪除符合條件的全部數據,若是隻刪除符合條件的第一條數據,則可使用model的findOneAndRemove()方法

Model.findOneAndRemove(conditions, [options], [callback])

  集合temps現有數據以下

clipboard.png

 如今刪除第一個年齡小於20的數據

temp.findOneAndRemove({age:{$lt:20}},function(err,doc){
    //{ _id: 5972d3f3e6f98ec60e3dc873, name: 'wang', age: 18 }
    console.log(doc);
})

clipboard.png

與model的remove()方法相同,回調函數不能省略,不然數據不會被刪除。固然,可使用exec()方法來簡寫代碼

temp.findOneAndRemove({age:{$lt:20}}).exec()

findByIdAndRemove()

Model.findByIdAndRemove(id, [options], [callback])

clipboard.png

刪除第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);
    })            
})

clipboard.png

 相似的,該方法也不能省略回調函數,不然數據不會被刪除。固然,可使用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上指定,相似於靜態方法或實例方法等

  能夠在數據庫執行下列操做時,設置先後鉤子

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設置的字段進行設置,分爲如下幾種狀況

  一、缺乏字段的文檔能夠保存成功

var temp = mongoose.model('temp', schema);
new temp({age:10}).save(function(err,doc){
    //{ __v: 0, age: 10, _id: 597304442b70086a1ce3cf05 }
    console.log(doc);
});

  二、包含未設置的字段的文檔也能夠保存成功,未設置的字段不被保存

new temp({age:100,abc:"abc"}).save(function(err,doc){
    //{ __v: 0, age: 100, _id: 5973046a2bb57565b474f48b }
    console.log(doc);
});

  三、包含字段類型與設置不一樣的字段的文檔也能夠保存成功,不一樣字段類型的字段被保存爲設置的字段類型

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的基本使用 有刪改

鏈接數據庫

編輯 test.js :

var mongoose = require('mongoose');
var db = mongoose.connect('mongodb://127.0.0.1:27017/test');
db.connection.on('error', function(error){
  console.log('數據庫test鏈接失敗:' + error);
});
db.connection.on('open', function(){
  console.log('數據庫test鏈接成功');
});

接着先打開一個 iTerm2 終端,開啓 mongodb 服務:

mongod

再打開另外一個 iTerm2 終端,運行 test.js:

node test.js
//成功後便會輸出:數據庫test鏈接成功

Schema/Model/Entity

沒有比文檔更詳細的了:http://mongoosejs.com/docs/gu...

Schema:數據庫集合的結構對象。
Model :由Schema構造而成,可操做數據庫。
Entity:由Model建立的實體,可操做數據庫。

看完文檔後,再看看下面一段代碼配合理解一下:

var mongoose = require("mongoose");
var db = mongoose.connect("mongodb://127.0.0.1:27017/test");
// var testModel = db.model('test1', testSchema); // 集合名稱;集合的結構對象
var TestSchema = new mongoose.Schema({
    name : { type:String },
    age  : { type:Number, default:0 },
    email: { type:String },
    time : { type:Date, default:Date.now }
});
var TestModel = db.model("test1", TestSchema );
var TestEntity = new TestModel({
    name : "helloworld",
    age  : 28,
    email: "helloworld@qq.com"
});
TestEntity.save(function(error,doc){
  if(error){
     console.log("error :" + error);
  }else{
     console.log(doc);
  }
});

model 數據插入

在前面的數據庫鏈接成功的前提下,咱們在數據庫 test 下新建一個集合 test1 、並往裏面插入保存一組數據:

var testSchema = new mongoose.Schema({
  name: {type: String},
  age: {type: Number, default: 0},
  email: {type: String},
  time: {type: Date, default: Date.now}
});
var testModel = db.model('test1', testSchema); // 集合名稱;集合的結構對象
// Document文檔(關聯數組式的對象) < Collection集合 < 數據庫
// 插入保存一段數據
testModel.create([
  {name: "test1", age: 8},
  {name: "test2", age: 18},
  {name: "test3", age: 28},
  {name: "test4", age: 38},
  {name: "test5", age: 48},
  {name: "test6", age: 58, email:"tttt@qq.com"},
  {name: "test7", age: 68, email:"ssss@qq.com"},
  {name: "test8", age: 18},
  {name: "test9", age: 18, email:"rrrr@qq.com"},
  {name: "test10",age: 18}
], function (error, docs) {
  if(error) {
    console.log(error);
  } else {
    console.log('save ok');
    console.log(docs);
  }
});

find 數據查詢

mongoose 提供了find、findOne、和findById方法用於文檔查詢。
基本語法:

model.find(Conditions,fields,options,callback(err, doc));
// Conditions: 查詢條件
// fields: 返回的字段
// options: 遊標(sort,limit)
// callback: 回調函數,參數doc爲查詢出來的結果

條件查詢的基礎:

$lt (小於<)
$lte (小於等於<=)
$gt (大於>)
$gte (大於等於>=)
$ne (不等於,不包含!=)
$in (包含)
$or (查詢多個鍵值的任意給定值)
$exists (判斷某些屬性是否存在)
$all (所有)

具體的一些實例,代碼裏已有詳細註釋:

// find(Conditions,fields,callback);
// 省略或爲空、返回全部記錄;只包含name,age字段,去掉默認的_id字段;執行回調函數
testModel.find({}, {name:1, age:1, _id:0}, function(err, docs){
  if (err) {
    console.log('查詢出錯:' + err);
  } else {
    console.log('{}查詢結果爲:');
    console.log(docs);
  }
});
----
{}查詢結果爲:
[ { name: 'test3', age: 28 },
  { name: 'test2', age: 18 },
  { name: 'test1', age: 8 },
  { name: 'test6', age: 58 },
  { name: 'test4', age: 38 },
  { name: 'test7', age: 68 },
  { name: 'test8', age: 18 },
  { name: 'test9', age: 18 },
  { name: 'test5', age: 48 },
  { name: 'test10', age: 18 } ]
----
// 查詢age大於等於28,小於等於48
testModel.find({age: {$gte: 28, $lte: 48}}, {name:1, age:1, _id:0}, function(err, docs){
  if (err) {
    console.log('查詢出錯:' + err);
  } else {
    console.log('$gte,$lte查詢結果爲:');
    console.log(docs);
  }
});
----
$gte,$lte查詢結果爲:
[ { name: 'test3', age: 28 },
  { name: 'test4', age: 38 },
  { name: 'test5', age: 48 } ]
----
// 查詢age爲5八、68的2條數據
testModel.find({age: {$in: [58, 68]}}, {name:1, age:1, _id:0}, function(err, docs){
  if (err) {
    console.log('查詢出錯:' + err);
  } else {
    console.log('$in查詢結果爲:');
    console.log(docs);
  }
});
----
$in查詢結果爲:
[ { name: 'test6', age: 58 }, { name: 'test7', age: 68 } ]
----
// 查詢name爲test三、或者age爲18的所有數據
testModel.find({$or: [{name: 'test3'}, {age: 18}]}, {name:1, age:1, _id:0}, function(err, docs){
  if (err) {
    console.log('查詢出錯:' + err);
  } else {
    console.log('$or查詢結果爲:');
    console.log(docs);
  }
});
----
$or查詢結果爲:
[ { name: 'test3', age: 28 },
  { name: 'test2', age: 18 },
  { name: 'test8', age: 18 },
  { name: 'test9', age: 18 },
  { name: 'test10', age: 18 } ]
----

// step3:遊標查詢
// 查詢name爲test三、或者age爲18的所有數據;但限制只查詢2條數據
testModel.find({$or: [{name: 'test3'}, {age: 18}]}, {name:1, age:1, _id:0}, {limit: 2}, function(err, docs){
  if (err) {
    console.log('查詢出錯:' + err);
  } else {
    console.log('limit查詢結果爲:');
    console.log(docs);
  }
});
----
limit查詢結果爲:
[ { name: 'test3', age: 28 }, { name: 'test2', age: 18 } ]
----

//查詢age大於等於28,小於等於48;降序輸出
testModel.find({age: {$gte: 28, $lte: 48}}, {name:1, age:1, _id:0}, {sort: {age: -1}}, function(err, docs){
  if (err) {
    console.log('查詢出錯:' + err);
  } else {
    console.log('sort查詢結果爲:');
    console.log(docs);
  }
});
----
sort查詢結果爲:
[ { name: 'test5', age: 48 },
  { name: 'test4', age: 38 },
  { name: 'test3', age: 28 } ]
----

update 數據更新

基本使用:model.update(查詢條件,更新對象,callback);

var conditions = {name: 'test1'};
var update = {$set: {age: 11 }};
testModel.update(conditions, update, function(error){
  if(error) {
    console.log(error);
  } else {
    console.log('Update success!');
    testModel.find({name: 'test1'}, {name:1, age:1, _id:0}, function(err, docs){
      if (err) {
        console.log('查詢出錯:' + err);
      } else {
        console.log('更新test1後的查詢結果爲:');
        console.log(docs);  
        // 更新test_update後的查詢結果爲空數組:[ ];
        // 更新test1後的查詢結果爲: [ { name: 'test1', age: 11 } ]
        // 只能更新原本已存在的數據
      }
    });
  }
---
Update success!
更新test1後的查詢結果爲:
[ { name: 'test1', age: 11 } ]
---

remove 數據刪除

基本使用:model.remove(查詢條件,callback);

var conditions = {name: 'test2'};
testModel.remove(conditions, function(error){
  if(error) {
    console.log(error);
  } else {
    console.log('Delete success!');
    testModel.find({name: 'test2'}, {name:1, age:1, _id:0}, function(err, docs){
      if (err) {
        console.log('查詢出錯:' + err);
      } else {
        console.log('刪除test2後的查詢結果爲:');
        console.log(docs);  // 刪除test2後的查詢結果爲空數組:[ ];
      }
    });
  }
});
----
Delete success!
刪除test2後的查詢結果爲:
[]
----

參考引用

  1. http://mongoosejs.com
  2. Mongoose簡要API
  3. Nodejs學習筆記(十四)— Mongoose介紹和入門
  4. Mongoose基礎入門
相關文章
相關標籤/搜索