Mongoose學習參考文檔——基礎篇

Mongoose學習參考文檔

前言:本學習參考文檔僅供參考,若有問題,師請雅正前端

1、快速通道

1.1 名詞解釋

  • Schema : 一種以文件形式存儲的數據庫模型骨架,不具有數據庫的操做能力node

  • Model : 由Schema發佈生成的模型,具備抽象屬性和行爲的數據庫操做對mongodb

  • Entity : 由Model建立的實體,他的操做也會影響數據庫數據庫

注意:編程

1.本學習文檔採用嚴格命名方式來區別不一樣對象,例如:數組

var PersonSchema;   //Person的文本屬性
    var PersonModel;    //Person的數據庫模型
    var PersonEntity;   //Person實體

2.SchemaModelEntity的關係請牢記,Schema生成ModelModel創造EntityModelEntity均可對數據庫操做形成影響,但ModelEntity更具操做性。安全

1.2 準備工做

1.首先你必須安裝MongoDBNodeJS前端工程師

2.在項目只可以建立一個數據庫鏈接,以下:app

var mongoose = require('mongoose');    //引用mongoose模塊
    var db = mongoose.createConnection('localhost','test'); //建立一個數據庫鏈接

3.打開本機localhosttest數據庫時,咱們能夠監測是否有異常異步

db.on('error',console.error.bind(console,'鏈接錯誤:'));
    db.once('open',function(){
      //一次打開記錄
    });

注意:

  成功開啓數據庫後,就能夠執行數據庫相應操做,假設如下代碼都在回調中處理

4.定義一個Schema

var PersonSchema = new mongoose.Schema({
      name:String   //定義一個屬性name,類型爲String
    });

5.將該Schema發佈爲Model

var PersonModel = db.model('Person',PersonSchema);
    //若是該Model已經發布,則能夠直接經過名字索引到,以下:
    //var PersonModel = db.model('Person');
    //若是沒有發佈,上一段代碼將會異常

6.用Model建立Entity

var personEntity = new PersonModel({name:'Krouky'});
    //打印這個實體的名字看看
    console.log(personEntity.name); //Krouky

7.咱們甚至能夠爲此Schema建立方法

//爲Schema模型追加speak方法
    PersonSchema.methos.speak = function(){
      console.log('個人名字叫'+this.name);
    }
    var PersonModel = db.model('Person',PersonSchema);
    var personEntity = new PersonModel({name:'Krouky'});
    personEntity.speak();//個人名字叫Krouky

8.Entity是具備具體的數據庫操做CRUD

personEntity.save();  //執行完成後,數據庫就有該數據了

9.若是要執行查詢,須要依賴Model,固然Entity也是能夠作到的

PersonModel.find(function(err,persons){
      //查詢到的全部person
    });

注意:

  1. 具體的如何配置SchemaModel以及ModelEntity的相關操做,咱們會在後面進行

  2. ModelEntity都有能影響數據庫的操做,但仍有區別,後面咱們也會作解釋

2、新手指引

  若是您還不清楚Mongoose是如何工做的,請參看第一章快速通道快速瀏覽他的用法吧

1. Schema——純潔的數據庫原型

1.1 什麼是Schema

  • 我理解Schema僅僅只是一斷代碼,他書寫完成後程序依然沒法使用,更沒法通往數據庫端
  • 他僅僅只是數據庫模型在程序片斷中的一種表現,或者是數據屬性模型

1.2 如何定義Schema

var BlogSchema = new Schema({
      title:String,
      author:String
      //new Schema()中傳入一個JSON對象,該對象形如 xxx:yyyy ,
      /xxx是一個字符串,定義了屬性,yyy是一個Schema.Type,定義了屬性類型
    });

1.3 什麼是Schema.Type

  Schema.Type是由Mongoose內定的一些數據類型,基本數據類型都在其中,他也內置了一些Mongoose特有的Schema.Type。固然,你也能夠自定義Schema.Type,只有知足Schema.Type的類型才能定義在Schema內。

1.4 Schema.Types

  NodeJS中的基本數據類型都屬於Schema.Type,另外Mongoose還定義了本身的類型

//舉例:
    var ExampleSchema = new Schema({
      name:String,
      binary:Buffer,
      living:Boolean,
      updated:Date,
      age:Number,
      mixed:Schema.Types.Mixed, //該混合類型等同於nested
      _id:Schema.Types.ObjectId,  //主鍵
      _fk:Schema.Types.ObjectId,  //外鍵
      array:[],
      arrOfString:[String],
      arrOfNumber:[Number],
      arrOfDate:[Date],
      arrOfBuffer:[Buffer],
      arrOfBoolean:[Boolean],
      arrOfMixed:[Schema.Types.Mixed],
      arrOfObjectId:[Schema.Types.ObjectId]
      nested:{
        stuff:String,
      }
    });

1.5 關於Buffer

  BufferArrayBufferNodejs兩種隱藏的對象,相關內容請查看NodeJS-API

1.6 關於Mixed

  Schema.Types.MixedMongoose定義個混合類型,該混合類型若是未定義具體形式。所以,若是定義具體內容,就直接使用{}來定義,如下兩句等價

var AnySchema = new Schema({any:{}});
    var AnySchema = new Schema({any:Schema.Types.Mixed});

  混合類型由於沒有特定約束,所以能夠任意修改,一旦修改了原型,則必須調用markModified()

person.anything = {x:[3,4,{y:'change'}]}
    person.markModified('anything');//傳入anything,表示該屬性類型發生變化
    person.save();

1.7 關於ObjectId

  主鍵,一種特殊並且很是重要的類型,每一個Schema都會默認配置這個屬性,屬性名爲_id,除非本身定義,方可覆蓋

var mongoose = require('mongoose');
    var ObjectId = mongoose.Schema.Types.ObjectId;
    var StudentSchema = new Schema({}); //默認會有_id:ObjectId
    var TeacherSchema = new Schema({id:ObjectId});//只有id:ObjectId

  該類型的值由系統本身生成,從某種意義上幾乎不會重複,生成過程比較複雜,有興趣的朋友能夠查看源碼。

1.8 關於Array

  ArrayJavaScript編程語言中並非數組,而是集合,所以裏面能夠存入不一樣的值,如下代碼等價:

var ExampleSchema1 = new Schema({array:[]});
    var ExampleSchema2 = new Schema({array:Array});
    var ExampleSchema3 = new Schema({array:[Schema.Types.Mixed]});
    var ExampleSchema4 = new Schema({array:[{}]});

1.9 附言

  Schema不只定義了文檔結構使用性能,還能夠有擴展插件實例方法靜態方法複合索引文檔生命週期鉤子

  Schema能夠定義插件,而且插件具備良好的可拔插性,請有興趣的讀者繼續日後閱讀或者查閱官方資料。

2. Schema的擴展

2.1 實例方法

  有的時候,咱們創造的Schema不只要爲後面的ModelEntity提供公共的屬性,還要提供公共的方法。

  下面例子比快速通道的例子更加高級,能夠進行高級擴展:

var PersonSchema = new Schema({name:String,type:String});
    //查詢相似數據
    PersonSchema.methods.findSimilarTypes = function(cb){
      return this.model('Person').find({type:this.type},cb);
    }

  使用以下:

var PersonModel = mongoose.model('Person',PersonSchema);
    var krouky = new PersonModel({name:'krouky',type:'前端工程師'});
krouky.findSimilarTypes(function(err,persons){ //persons中就能查詢到其餘前端工程師 });

2.2 靜態方法

  靜態方法在Model層就能使用,以下:

PersonSchema.statics.findByName = function(name,cb){
    this.find({name:new RegExp(name,'i'),cb});
  }
  var PersonModel = mongoose.model('Person',PersonSchema);
  PersonModel.findByName('krouky',function(err,persons){
    //找到全部名字叫krouky的人
  });

2.3 索引

  索引或者複合索引能讓搜索更加高效,默認索引就是主鍵索引ObjectId,屬性名爲_id, 索引會做爲一個專題來說解

2.4 虛擬屬性

  Schema中若是定義了虛擬屬性,那麼該屬性將不寫入數據庫,例如:

var PersonSchema = new Schema({
      name:{
        first:String,
        last:String
      }
    });
    var PersonModel = mongoose.model('Person',PersonSchema);
    var krouky = new PersonModel({
      name:{first:'krouky',last:'han'}
    });

  若是每次想使用全名就得這樣

console.log(krouky.name.first + ' ' + krouky.name.last);

  顯然這是很麻煩的,咱們能夠定義虛擬屬性

PersonSchema.virtual('name.full').get(function(){
      return this.name.first + ' ' + this.name.last;
    });

  那麼就能用krouky.name.full來調用全名了,反之若是知道full,也能夠反解firstlast屬性

PersonSchema.virtual('name.full').set(function(name){
      var split = name.split(' ');
      this.name.first = split[0];
      this.name.last = split[1];
    });
    var PersonModel = mongoose.model('Person',PersonSchema);
    var krouky = new PersonModel({});
    krouky.name.full = 'krouky han';//會被自動分解
    console.log(krouky.name.first);//krouky

2.5 配置項

  在使用new Schema(config)時,咱們能夠追加一個參數options來配置Schema的配置,形如:

var ExampleSchema = new Schema(config,options);

  或者使用

var ExampleSchema = new Schema(config);
    ExampleSchema.set(option,value);

  可供配置項有:safestrictcappedversionKeyautoIndex

2.5.1 safe——安全屬性(默認安全)

  通常可作以下配置:

new Schema({...},{safe:true});

  固然咱們也能夠這樣

new Schema({...},{safe:{j:1,w:2,wtimeout:10000}});

  j表示作1份日誌,w表示作2個副本(尚不明確),超時時間10秒

2.5.2 strict——嚴格配置(默認啓用)

  確保Entity的值存入數據庫前會被自動驗證,若是你沒有充足的理由,請不要停用,例子:

var ThingSchema = new Schema({a:String});
    var ThingModel = db.model('Thing',SchemaSchema);
    var thing = new Thing({iAmNotInTheThingSchema:true});
    thing.save();//iAmNotInTheThingSchema這個屬性將沒法被存儲

  若是取消嚴格選項,iAmNotInTheThingSchema將會被存入數據庫

  該選項也能夠在構造實例時使用,例如:

var ThingModel = db.model('Thing');
    var thing1 = new ThingModel(doc,true);  //啓用嚴格
    var thing2 = new ThingModel(doc,false); //禁用嚴格

注意:

  strict也能夠設置爲throw,表示出現問題將會拋出錯誤

2.5.3 shardKey

  須要mongodb作分佈式,纔會使用該屬性

2.5.4 capped——上限設置

  若是有數據庫的批量操做,該屬性能限制一次操做的量,例如:

new Schema({...},{capped:1024});  //一次操做上線1024條數據

  固然該參數也但是JSON對象,包含size、max、autiIndexId屬性

new Schema({...},{capped:{size:1024,max:100,autoIndexId:true}});
2.5.5 versionKey——版本鎖

  版本鎖是Mongoose默認配置(__v屬性)的,若是你想本身定製,以下:

new Schema({...},{versionKey:'__someElse'});

  此時存入數據庫的版本鎖就不是__v屬性,而是__someElse,至關因而給版本鎖取名字。

  具體怎麼存入都是由MongooseMongoDB本身決定,固然,這個屬性你也能夠去除

new Schema({...},{versionKey:false});

  除非你知道你在作什麼,而且你知道這樣作的後果

2.5.6 autoIndex——自動索引

  該內容將在索引章節單獨講解

3. Documents

  Document是與MongoDB文檔一一對應的模型,Document可等同於Entity,具備屬性和操做性

注意:

  Document的`CRUD都必須通過嚴格驗證的,參看2.5.2 Schema的strict嚴格配置

3.1 查詢

  查詢內容過多,專題講解

3.2 更新

  有許多方式來更新文件,如下是經常使用的傳統方式:

PersonModel.findById(id,function(err,person){
      person.name = 'MDragon';
      person.save(function(err){});
    });

  這裏,利用Model模型查詢到了person對象,該對象屬於Entity,能夠有save操做,若是使用Model`操做,需注意:

PersonModel.findById(id,function(err,person){
      person.name = 'MDragon';
      var _id = person._id; //須要取出主鍵_id
      delete person._id;    //再將其刪除
      PersonModel.update({_id:_id},person,function(err){});
      //此時才能用Model操做,不然報錯
    });

  update第一個參數是查詢條件,第二個參數是更新的對象,但不能更新主鍵,這就是爲何要刪除主鍵的緣由。

  固然這樣的更新很麻煩,可使用$set屬性來配置,這樣也不用先查詢,若是更新的數據比較少,可用性仍是很好的:

PersonModel.update({_id:_id},{$set:{name:'MDragon'}},function(err){});

  須要注意,DocumentCRUD操做都是異步執行,callback第一個參數必須是err,而第二個參數各個方法不同,updatecallback第二個參數是更新的數量,若是要返回更新後的對象,則要使用以下方法

Person.findByIdAndUpdate(_id,{$set:{name:'MDragon'}},function(err,person){
      console.log(person.name); //MDragon
    });

  相似的方法還有findByIdAndRemove,如同名字,只能根據id查詢並做update/remove操做,操做的數據僅一條

3.3 新增

  若是是Entity,使用save方法,若是是Model,使用create方法

//使用Entity來增長一條數據
    var krouky = new PersonModel({name:'krouky'});
    krouky.save(callback);
    //使用Model來增長一條數據
    var MDragon = {name:'MDragon'};
    PersonModel.create(MDragon,callback);

  兩種新增方法區別在於,若是使用Model新增時,傳入的對象只能是純淨的JSON對象,不能是由Model建立的實體,緣由是:由Model建立的實體krouky雖然打印是隻有{name:'krouky'},可是krouky屬於Entity,包含有Schema屬性和Model數據庫行爲模型。若是是使用Model建立的對象,傳入時必定會將隱藏屬性也存入數據庫,雖然3.x追加了默認嚴格屬性,但也沒必要要增長操做的報錯

3.4 刪除

  和新增同樣,刪除也有2種方式,但EntityModel都使用remove方法

4.Sub Docs

  如同SQL數據庫中2張表有主外關係,Mongoose將2個Document的嵌套叫作Sub-Docs(子文檔)

  簡單的說就是一個Document嵌套另一個Document或者Documents:

var ChildSchema1 = new Schema({name:String});
    var ChildSchema2 = new Schema({name:String});
    var ParentSchema = new Schema({
      children1:ChildSchema1,   //嵌套Document
      children2:[ChildSchema2]  //嵌套Documents
    });

  Sub-Docs享受和Documents同樣的操做,可是Sub-Docs的操做都由父類去執行

var ParentModel = db.model('Parent',parentSchema);
    var parent = new ParentModel({
      children2:[{name:'c1'},{name:'c2'}]
    });
    parent.children2[0].name = 'd';
    parent.save(callback);

  parent在執行保存時,因爲包含children2,他是一個數據庫模型對象,所以會先保存chilren2[0]chilren2[1]

  若是子文檔在更新時出現錯誤,將直接報在父類文檔中,能夠這樣處理:

ChildrenSchema.pre('save',function(next){
      if('x' === this.name) return next(new Error('#err:not-x'));
      next();
    });
    var parent = new ParentModel({children1:{name:'not-x'}});
    parent.save(function(err){
      console.log(err.message); //#err:not-x
    });

4.1 查詢子文檔

  若是childrenparent的子文檔,能夠經過以下方法查詢到children

var child = parent.children.id(id);

4.2 新增、刪除、更新

  子文檔是父文檔的一個屬性,所以按照屬性的操做便可,不一樣的是在新增父類的時候,子文檔是會被先加入進去的。

  若是ChildrenSchema是臨時的一個子文檔,不做爲數據庫映射集合,能夠這樣:

var ParentSchema = new Schema({
      children:{
        name:String
      }
    });
    //其實就是匿名混合模式

5.Model

5.1 什麼是Model

  Model模型,是通過Schema構造來的,除了Schema定義的數據庫骨架之外,還具備數據庫行爲模型,他至關於管理數據庫屬性、行爲的類

5.2 如何建立Model

  你必須經過Schema來建立,以下:

//先建立Schema
    var TankSchema = new Schema({
      name:'String',
      size:'String' 
    });
    //經過Schema建立Model
    var TankModel = mongoose.model('Tank',TankSchema);

5.2 操做Model

  該模型就能直接拿來操做,具體查看API,例如:

var tank = {'something',size:'small'};
    TankModel.create(tank);

注意:

  你可使用Model來建立EntityEntity實體是一個特有Model具體對象,可是他並不具有Model的方法,只能用本身的方法。

//經過Model建立Entity
  var tankEntity = new TankModel('someother','size:big');
  tankEntity.save();

6.Query

  查詢是數據庫中運用最多也是最麻煩的地方,這裏對Query解讀的並不完善,僅僅是本身的一點領悟而已。

6.1 查詢的方式

  一般有2種查詢方式,一種是直接查詢,一種是鏈式查詢(2種查詢都是本身命名的)

6.1.1 直接查詢

  在查詢時帶有回調函數的,稱之爲直接查詢,查詢的條件每每經過API來設定,例如:

PersonModel.findOne({'name.last':'dragon'},'some select',function(err,person){
      //若是err==null,則person就能取到數據
    });

  具體的查詢參數,請查詢API

6.1.2 鏈式查詢

  在查詢時候,不帶回調,而查詢條件經過API函數來制定,例如:

var query = PersonModel.findOne({'name.last':'dragon'});
    query.select('some select');
    query.exec(function(err,pserson){
    //若是err==null,則person就能取到數據
  });

  這種方式相對直接查詢,分的比較明細,若是不帶callback,則返回queryquery沒有執行的預編譯查詢語句,該query對象執行的方法都將返回本身,只有在執行exec方法時才執行查詢,並且必須有回調。

  由於query的操做始終返回自身,咱們能夠採用更形象的鏈式寫法

Person
      .find({ occupation: /host/ })
      .where('name.last').equals('Ghost')
      .where('age').gt(17).lt(66)
      .where('likes').in(['vaporizing', 'talking'])
      .limit(10)
      .sort('-occupation')
      .select('name occupation')
      .exec(callback);

7.Validation

  數據的存儲是須要驗證的,不是什麼數據都能往數據庫裏丟或者顯示到客戶端的,數據的驗證須要記住如下規則:

  • 驗證始終定義在SchemaType
  • 驗證是一個內部中間件
  • 驗證是在一個Document被保存時默認啓用的,除非你關閉驗證
  • 驗證是異步遞歸的,若是你的SubDoc驗證失敗,Document也將沒法保存
  • 驗證並不關心錯誤類型,而經過ValidationError這個對象能夠訪問

7.1 驗證器

  1. required 非空驗證
  2. min/max 範圍驗證(邊值驗證)
  3. enum/match 枚舉驗證/匹配驗證
  4. validate 自定義驗證規則

  如下是綜合案例:

var PersonSchema = new Schema({
      name:{
        type:'String',
        required:true //姓名非空
      },
      age:{
        type:'Nunmer',
        min:18,       //年齡最小18
        max:120     //年齡最大120
      },
      city:{
        type:'String',
        enum:['北京','上海']  //只能是北京、上海人
      },
      other:{
        type:'String',
        validate:[validator,err]  //validator是一個驗證函數,err是驗證失敗的錯誤信息
      }
    });

7.2 驗證失敗

  若是驗證失敗,則會返回err信息,err是一個對象該對象屬性以下

err.errors                //錯誤集合(對象)
    err.errors.color          //錯誤屬性(Schema的color屬性)
    err.errors.color.message  //錯誤屬性信息
    err.errors.path             //錯誤屬性路徑
    err.errors.type             //錯誤類型
    err.name                //錯誤名稱
    err.message                 //錯誤消息

  一旦驗證失敗,ModelEntity都將具備和err同樣的errors屬性

8.Middleware中間件

8.1 什麼是中間件

  中間件是一種控制函數,相似插件,能控制流程中的init、validatesaveremove`方法

8.2 中間件的分類

  中間件分爲兩類

8.2.1 Serial串行

  串行使用pre方法,執行下一個方法使用next調用

var schema = new Schema(...);
    schema.pre('save',function(next){
      //作點什麼
      next();
    });
8.2.2 Parallel並行

  並行提供更細粒度的操做

var schema = new Schema(...);
    schema.pre('save',function(next,done){
      //下一個要執行的中間件並行執行
      next();
      doAsync(done);
    });

8.3 中間件特色

  一旦定義了中間件,就會在所有中間件執行完後執行其餘操做,使用中間件能夠霧化模型,避免異步操做的層層迭代嵌套

8.4 使用範疇

  1. 複雜的驗證
  2. 刪除有主外關聯的doc
  3. 異步默認
  4. 某個特定動做觸發異步任務,例如觸發自定義事件和通知

  例如,能夠用來作自定義錯誤處理

schema.pre('save',function(next){
      var err = new Eerror('some err');
      next(err);
    });
    entity.save(function(err){
      console.log(err.message); //some err
    });
相關文章
相關標籤/搜索