深入淺出mongoose

chengkai發表於2019-03-03

mongoose是nodeJS提供連線 mongodb的一個庫,類似於jquery和js的關係,對mongodb一些原生方法進行了封裝以及優化。簡單的說,Mongoose就是對node環境中MongoDB資料庫操作的封裝,一個物件模型工具,將資料庫中的資料轉換為JavaScript物件以供我們在應用中使用。

install mongoose

npm install mongoose --save
複製程式碼
const mongoose = require(`mongoose`),
      connection = mongoose.connect(`mongodb://127.0.0.1:27017/wechatShop`, {
        useMongoClient: true
      });
複製程式碼

Schema

Mongoose的一切都始於一個Schema。每個schema對映到MongoDB的集合(collection)和定義該集合(collection)中的文件的形式。
Schemas不僅定義了文件和屬性的結構,還定義了文件例項方法、靜態模型方法、複合索引和文件被稱為中介軟體的生命週期鉤子。

var blogSchema = 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
  }
});
複製程式碼

Models

使用我們的schema定義,我們需要將我們的blogschema轉成我們可以用的模型。為此,我們通過mongoose.model(modelName, schema)。

var Blog = mongoose.model(`Blog`, blogSchema);
複製程式碼

例項方法

模型的例項是文件(documents)。文件有許多自己內建的例項方法。我們也可以定義我們自己的自定義文件例項方法。

var Blog = mongoose.model(`Blog`, blogSchema);
var blog = new Schema({ name: String, type: String });//模型例項
複製程式碼

常用的內建例項方法有:

remove、set、get、invalidate、populate、save等

詳細api可查閱官方文件內建的例項方法

下面我們來說說自定義例項方法。自定義例項方法,就是對當前例項的methods擴充套件方法即可,如下所示:

animalSchema.methods.findSimilarTypes = function(cb) {
  return this.model(`Animal`).find({ type: this.type }, cb);
};
複製程式碼

靜態方法

常用的內建靜態方法有:

create、find、findOne等

給一個模型新增靜態方法的也是很簡單。繼續我們的animalschema:

animalSchema.statics.findByName = function(name, cb) {
  return this.find({ name: new RegExp(name, `i`) }, cb);
};

var Animal = mongoose.model(`Animal`, animalSchema);
Animal.findByName(`fido`, function(err, animals) {
  console.log(animals);
});
複製程式碼

查詢助手

您還可以像例項方法那樣新增查詢輔助功能,這是,但對於mongoose的查詢。查詢輔助方法可以讓你擴充套件mongoose鏈式查詢生成器API。

animalSchema.query.byName = function(name) {
  return this.find({ name: new RegExp(name, `i`) });
};

var Animal = mongoose.model(`Animal`, animalSchema);
Animal.find().byName(`fido`).exec(function(err, animals) {
  console.log(animals);
});
複製程式碼

索引

mongoose同樣也對索引做了處理,在mongoose中定義索引有兩種方法。

第一種:直接在schema裡面定義,如下所示

var User = mongoose.model(`User`, {
  username: {
      type: String,
      index: true
  },
  password: String
})
複製程式碼

第二種:統一定義索引

var User = mongoose.model(`User`, {
  username: {
      type: String
  },
  password: String
});

User.index({
    username: 1 / -1  (正向和逆向)
})
複製程式碼

關閉索引:

mongoose.connect(`mongodb://user:pass@localhost:port/database`, { config: { autoIndex: false } });
// or  
mongoose.createConnection(`mongodb://user:pass@localhost:port/database`, { config: { autoIndex: false } });
// or
animalSchema.set(`autoIndex`, false);
// or
new Schema({..}, { autoIndex: false });
複製程式碼

注意:索引濫用會導致很嚴重的效能問題,建議合理使用索引。

虛擬屬性(針對例項即文件)

虛擬屬性 是文件屬性,您可以獲取和設定但不儲存到MongoDB。用於格式化或組合欄位,從而制定者去組成一個單一的值為儲存多個值是有用的。

下面我們先定義一個例項

// define a schema
var personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
});

// compile our model
var Person = mongoose.model(`Person`, personSchema);

// create a document
var bad = new Person({
    name: { first: `Walter`, last: `White` }
});
複製程式碼

假設我們想列印bad的全名。我們可以這樣做:

console.log(bad.name.first + ` ` + bad.name.last); // Walter White
複製程式碼

或者我們可以在personschema定義 虛擬屬性的getter ,這樣我們不需要每次寫出這個字串的拼接:

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

console.log(`%s is insane`, bad.name.full); // Walter White is insane
複製程式碼

注意,這裡的虛擬屬性並沒有存入資料庫,所以如果是直接獲取,是獲取不到值的。

驗證

在我們進入驗證語法的細節之前,請記住以下的規則:

驗證是在SchemaType定義

驗證是中介軟體。Mongoose暫存器驗證作為pre(`save`)鉤子在每個模式預設情況下。

你可以手動使用doc執行驗證。validate(callback) or doc.validateSync()。

驗證程式不執行在未定義的值上。唯一的例外是required驗證器。

驗證非同步遞迴;當你呼叫Model#save,子文件驗證也可以執行。如果出現錯誤,你的 Model#save回撥接收它。

驗證是可定製的。

 var schema = new Schema({
      name: {
        type: String,
        required: true
      }
    });
    var Cat = db.model(`Cat`, schema);

    // This cat has no name :(
    var cat = new Cat();
    cat.save(function(error) {
      assert.equal(error.errors[`name`].message,
        `Path `name` is required.`);

      error = cat.validateSync();
      assert.equal(error.errors[`name`].message,
        `Path `name` is required.`);
    });
複製程式碼
Mongoose有幾個內建驗證器。

所有的schematypes有內建的require驗證器。所需的驗證器使用SchemaType的checkrequired()函式確定的值滿足所需的驗證器。

數值( Numbers )有最大(man)和最小(min)的驗證器。

字串(String)有列舉,match,maxLength和minLength驗證器。

每一個上述的驗證連結提供更多的資訊關於如何使他們和自定義錯誤資訊。

 var breakfastSchema = new Schema({
      eggs: {
        type: Number,
        min: [6, `Too few eggs`],
        max: 12
      },
      bacon: {
        type: Number,
        required: [true, `Why no bacon?`]
      },
      drink: {
        type: String,
        enum: [`Coffee`, `Tea`]
      }
    });
    var Breakfast = db.model(`Breakfast`, breakfastSchema);

    var badBreakfast = new Breakfast({
      eggs: 2,
      bacon: 0,
      drink: `Milk`
    });
    var error = badBreakfast.validateSync();
    assert.equal(error.errors[`eggs`].message,
      `Too few eggs`);
    assert.ok(!error.errors[`bacon`]);
    assert.equal(error.errors[`drink`].message,
      ``Milk` is not a valid enum value for path `drink`.`);

    badBreakfast.bacon = null;
    error = badBreakfast.validateSync();
    assert.equal(error.errors[`bacon`].message, `Why no bacon?`);
複製程式碼
自定義驗證

如果內建驗證器是不夠的話,你可以自定義驗證器來適應你的需求。

var userSchema = new Schema({
      phone: {
        type: String,
        validate: {
          validator: function(v) {
            return /d{3}-d{3}-d{4}/.test(v);
          },
          message: `{VALUE} is not a valid phone number!`
        },
        required: [true, `User phone number required`]
      }
    });
複製程式碼
驗證的錯誤處理

驗證失敗後Errors返回一個錯誤的物件實際上是validatorerror物件。每個ValidatorError有kind, path, value, and message屬性。

var Person = db.model(`Person`, personSchema);
var person = new Person();

var error = person.validateSync();
複製程式碼

聯表

如果你使用過mysql,肯定用過join,用來聯表查詢,但mongoose中並沒有join,不過它提供了一種更方便快捷的辦法,Population。

首先定義兩個model。

var User = mongoose.model(`User`, {
  username: String,
  password: String
})

var News = mongoose.model(`News`, {
  title: String,
  author: {
    type: mongoose.Schema.ObjectId,
    ref: `User`
  }
});
複製程式碼

然後新增資料

User.create({username:`ck`,username:`滴滴`},function(err, data){
  console.log(data)
  News.create({title:`title`,author:data},function(){

  })
})
複製程式碼

列印查詢結果到控制檯

News.findOne().populate(`author`,`username`).exec(function(err, doc){
  console.log(`==============`,err, doc)
  //    ============== null { _id: 5a41d2e16f78a43c5873fe03,
  //    title: `title`,
  //    author: { _id: 5a41d2e16f78a43c5873fe02, username: `ck` 
  //    },
  //    __v: 0 }
})
複製程式碼
歡迎各路大佬對文章錯誤的地方進行指正,萬分感謝,共同學習進步。博主的GitHub地址,可以在GitHub提Issues或者直接在文章下面評論區留言。