Mongoose初使用總結

參考深入淺出mongoose

連接mongoose

mongoose連接數據庫有兩種方式
第一種:

'use strict';

const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/test');
const con = mongoose.connection;
con.on('error', console.error.bind(console, '連接數據庫失敗'));
con.once('open',()=>{
    //成功連接
})

第二種:

var mongoose = require('mongoose');

db = mongoose.createConnection('localhost', 'test');
var schema = new mongoose.Schema({ name: String });
var collectionName = 'kittens';
var M = db.model('Kitten', schema, collectionName);
var silence = new M({ name: "Silence"});
silence.save(function(err){
 
});

mongoose.createConnection()和mongoose.connect()區別

首先,我們需要定義一個連接,如果你的app只用到一個數據庫,你應該使用 mongoose.connect。如果你還需要連接其他數據庫,使用mongoose.createConnection。
所有的 connect and createConnection 使用 mongodb:// URI, or the parameters host, database, port, options.等參數

var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/my_database');

一旦連接,Connection對象會觸發open事件,如果你使用 mongoose.connect, Connection對象就是默認的 mongoose.connection,而使用 mongoose.createConnection 返回值就是 Connection.

Mongoose會緩存所有命令直到連接上數據庫,這意味著你不必等待它連接MongoDB再定義 models,執行 queries 等。

Mongoose基本概念

  • Schema: 表定義模板
  • Model: 類似關系數據庫表,封裝成具有一些集合操作的對象
  • instance: 類似記錄,由Model創建的實體,也具有影響數據庫的操作

基本用法

//定義一個schema
    let Schema = mongoose.Schema({
        category:String,
        name:String
    });
    Schema.methods.eat = function(){
        console.log("I've eatten one "+this.name);
    }
    //繼承一個schema
    let Model = mongoose.model("fruit",Schema);
    //生成一個document
    let apple = new Model({
        category:'apple',
        name:'apple'
    });
    //存放數據
    apple.save((err,apple)=>{
        if(err) return console.log(err);
        apple.eat();
        //查找數據
        Model.find({name:'apple'},(err,data)=>{
            console.log(data);
        })
    });

Schema

// from mongoose author
var mongoose = require('mongoose');
var Schema = mongoose.Schema;//引用出來,不需要每次調用 mongoose.Schema()這個丑陋的API.

var blogSchema = new Schema({
  title:  String,
  author: String,
  //直接寫法,會被轉化成相應的SchemaType 
  body:   String,  
  comments: [{ body: String, date: Date }],
  //定義SchemaType寫法
  date: { type: Date, default: Date.now },
  hidden: Boolean,
  meta: {
    votes: Number,
    favs:  Number
  }
});

Schema 之所以能夠定義documents, 是因為他可以限制你輸入的字段及其類型. mongoose支持的基本類型有:

  • String
  • Number
  • Date
  • Buffer
  • Boolean
  • Mixed
  • ObjectId
  • Array

SchemaType

type屬性指定SchemaType類型,不同的SchemaType類型還有其他不同的屬性配置

var schema2 = new Schema({
  test: {
    type: String,
    lowercase: true // Always convert `test` to lowercase
  }
});

這是所有類型公有的:

  • required: 必選驗證器。
  • default: 默認值。Any或function,如果該值是一個函數,則該函數的返回值將用作默認值。
  • select: boolean值, 指定是否被投影
  • `validate: 驗證器
  • get: get方法,using Object.defineProperty().
  • set: set方法 using Object.defineProperty().
  • alias: 別名。

其他類型特有屬性官方API查找。

設置索引

這里設置索引分兩種,一種設在Schema filed, 另外一種設在 Schema.index 里.

//在field 設置
var animalSchema = new Schema({
  name: String,
  type: String,
  tags: { type: [String], index: true } 
});

//在Schema.index中設置.

animalSchema.index({ name: 1, type: -1 });



//1 表示正序, -1 表示逆序

實際上,兩者效果是一樣的. 看每個人的喜好了. 不過推薦直接在Schema level中設置, 這樣分開能夠增加可讀性. 不過,
可以說,當應用啟動的時候, ,Mongoose會自動為Schema中每個定義了索引的調用ensureIndex,確保生成索引.
并在所有的secureIndex調用成功或出現錯誤時,在 Model 上發出一個'index'事件。 開發環境用這個很好, 但是建議在生產環境不要使用這個.使用下面的方法禁用ensureIndex。
通過將 Schema 的autoIndex選項設置為false或通過將選項config.autoIndex設置為false將連接全局設置為禁用此行為 有可能嚴重拖慢查詢或者創建速度,所以一般而言,我們需要將該option 關閉.

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 }); //懶癌不推薦
// Will cause an error because mongodb has an _id index by default that
// is not sparse
animalSchema.index({ _id: 1 }, { sparse: true });
var Animal = mongoose.model('Animal', animalSchema);

Animal.on('index', function(error) {
  // "_id index cannot be sparse"
  console.log(error.message);
});

定義Schema.methods

// 定義一個schema
var freshSchema = new Schema({ name: String, type: String });

// 添加一個fn. 
animalSchema.methods.findSimilarTypes = function (cb) {
  //這里的this指的是具體document上的this
  //this.model 返回Model對象
  return this.model ('Animal').find({ type: this.type }, cb);
}
// 實際上,我們可以通過schema綁定上,數據庫操作的所有方法.
// 該method實際上是綁定在 實例的 doc上的

實例Model

這里同樣很簡單,只需要 mongoose.model() 即可.

//生成,model 類. 實際上就相當于我們的一個collection
var Animal = mongoose.model('Animal', animalSchema);
var dog = new Animal({ type: 'dog' });

但是, 這里有個問題. 我們在Schema.methods.fn 上定義的方法,只能在 new Model() 得到的實例中才能訪問. 那如果我們想,直接在Model上調用 相關的查詢或者刪除呢?

綁定Model方法

同樣很簡單,使用 Statics 即可.

// 給model添加一個findByName方法
animalSchema.statics.findByName = function (name, cb) {
  //這里的this 指的就是Model
  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 還有一個super featrue-- virtual property 該屬性是直接設置在Schema上的. 但是,需要注意的是,VR 并不會真正的存放在db中. 他只是一個提取數據的方法.

//schema基本內容
var personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
});

// 生成Model
var Person = mongoose.model('Person', personSchema);

//現在我們有個需求,即,需要將first和last結合輸出.
//一種方法是,使用methods來實現
//schema 添加方法
personSchema.methods.getName = function(){
    return this.first+" "+this.last;
}

// 生成一個doc
var bad = new Person({
    name: { first: 'jimmy', last: 'Gay' }
});

//調用
bad.getName();

但是,像這樣,僅僅這是為了獲取一個屬性, 實際上完全可以使用虛擬屬性來實現.

//schema 添加虛擬屬性
personSchema.virtual('fullName').get(function(){
    return this.first+" "+this.last;
})
//調用
bad.fullName;  //和上面的方法的結果是完全一致的

而且,經過測試, 使用fn實現的返回,比VR 要慢幾十倍. 一下是測試結果:

console.time(1);
    bad.getName();
    console.timeEnd(1);
    console.time(2);
    bad.fullName;
    console.timeEnd(2);
    
    //結果為:
    1: 4.323ms;  //method
    2: 0.253ms  // VR

最后再補充一下,Schema中初始化的相關參數.
Schema參數 在 new Schema([options]) 中,我們需要設置一些相關的參數.

  • safe: 用來設置安全模式. 實際上,就是定義入庫時數據的寫入限制. 比如寫入時限等.
//使用安全模式. 表示在寫入操作時,如果發生錯誤,也需要返回信息.
 var safe = true;
new Schema({ .. }, { safe: safe });

// 自定義安全模式. w為寫入的大小范圍. wtimeout設置寫入時限. 如果超出10s則返回error
var safe = { w: "majority", wtimeout: 10000 };
new Schema({ .. }, { safe: safe });
  • toObject: 用來表示在提取數據的時候, 把documents 內容轉化為Object內容輸出. 一般而言只需要設置getters為true即可.
var schema = new Schema({ name: String });
schema.path('name').get(function (v) {
  return v + ' is my name';
});
schema.set('toObject', { getters: true });
var M = mongoose.model('Person', schema);
var m = new M({ name: 'Max Headroom' });
console.log(m); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }
  • toJSON: 該是和toObject一樣的使用. 通常用來把 documents 轉化為Object. 但是, 需要顯示使用toJSON()方法,
var schema = new Schema({ name: String });
schema.path('name').get(function (v) {
  return v + ' is my name';
});
schema.set('toJSON', { getters: true, virtuals: false });
var M = mongoose.model('Person', schema);
var m = new M({ name: 'Max Headroom' });
console.log(m.toObject()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom' }
console.log(m.toJSON()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }
// since we know toJSON is called whenever a js object is stringified:
console.log(JSON.stringify(m)); // { "_id": "504e0cd7dd992d9be2f20b6f", "name": "Max Headroom is my name" }

Model

model的創建

model的創建實際上就是方法的copy. 將schema上的方法,copy到model上. 只是copy的位置不一樣, 一部分在prototype上, 一部分在constructor中.

//from mongoosejs
var schema = new mongoose.Schema({ name: 'string', size: 'string' });
var Tank = mongoose.model('Tank', schema);

這里,我們一定要搞清楚一個東西. 實際上, mongoose.model里面定義的第一個參數,比如’Tank’, 并不是數據庫中的, collection. 他只是collection的單數形式, 實際上在db中的collection是’Tanks’.

想兩邊名稱保持一致,可參考http://aiilive.blog.51cto.com/1925756/1405203

model 的子文檔操作

本來mongodb是沒有關系的. 但是, mongoose提供了children字段. 讓我們能夠輕松的在表間建立關系. 現在,我們來創建一個子域:

var childSchema = new Schema({ name: 'string' });

var parentSchema = new Schema({
  children: [childSchema]   //指明sub-doc的schema
});
//在創建中指明doc
var Parent = mongoose.model('Parent', parentSchema);
var parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] })
parent.children[0].name = 'Matthew';
parent.save(callback);

現在, 我們就已經創建了3個table. 一個parent 包含了 兩個child 另外,如果我們想要查詢指定的doc。 則可以使用 id()方法.

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

子文檔的CRUD, 實際上就是數組的操作, 比如push,unshift,remove,pop,shift等

parent.children.push({ name: 'Liesl' });

mongoose還給移除提供了另外一個方法–remove:

var doc = parent.children.id(id).remove();

如果你忘記添加子文檔的話,可以在外圍添加, 但是字段必須在Schema中指定

var newdoc = parent.children.create({ name: 'Aaron' });

document的CRUD操作

document 的創建 關于document的創建,有兩種方法, 一種是使用document實例創建,另外一種是使用Model類創建.

document的創建

var Tank = mongoose.model('Tank', yourSchema);

var small = new Tank({ size: 'small' });
//使用實例創建
small.save(function (err) {
  if (err) return handleError(err);
  // saved!
})

//使用Model類創建
Tank.create({ size: 'small' }, function (err, small) {
  if (err) return handleError(err);
  // saved!
})

document的查詢

Mongoose查找文檔很容易,它支持MongoDB的豐富的查詢語法。 可以使用每個models findfindByIdfindOnewhere 等靜態方法進行查找文檔。
事實上,在mongoose中,query數據 提供了兩種方式.

  • callback: 使用回調函數, 即, query會立即執行,然后返回到回調函數中.
Person.findOne({ 'name.last': 'Ghost' }, 'name occupation', function (err, person) {
  if (err) return handleError(err);
 // get data
})
  • query: 使用查詢方法,返回的是一個Query對象. 該對象是一個Promise, 所以可以使用 chain 進行調用.最后必須使用exec(cb)傳入回調進行處理. cb 是一個套路, 第一個參數永遠是err. 第二個就是返回的數據。
Tank.find({ size: 'small' }).where('createdDate').gt(oneYearAgo).exec(callback);

以下兩種等價寫法:

// With a JSON doc
Person.
  find({
    occupation: /host/,
    'name.last': 'Ghost',
    age: { $gt: 17, $lt: 66 },
    likes: { $in: ['vaporizing', 'talking'] }
  }).
  limit(10).
  sort({ occupation: -1 }).
  select({ name: 1, occupation: 1 }).
  exec(callback);
  
// Using query builder
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);

Query Helpers

你能夠添加 query helper functions,跟定義在Schema實例方法一樣,但是返回query對象作為mongoose queries使用(說句白了就是封裝mongoose查詢方法). Query helper methods 使你能夠擴展mongoose's chainable query builder 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);
});

上面4個API, 3個使用方式都是一樣的, 另外一個不同的是where. 他一樣是用來進行query. 只是,寫法和find系列略有不同.

where簡介 where的API為: Model.where(path, [val]) path實際上就是字段, 第二個參數.val表示可以用來指定,path = val的數據內容, 你也可以不寫, 交給后面進行篩選. 看一下對比demo吧:

User.find({age: {$gte: 21, $lte: 65}}, callback);
//等價于:
User.where('age').gte(21).lte(65).exec(callback);

從上面的query中,我們可以看到有許多fn, 比如gte,lte,$gte,$lte. 這些是db提供給我們用來查詢的快捷函數. 我們可以參考, mongoose給的參考: query Helper fn

另外還有一些游標集合的處理方法: 常用的就3個, limit,skip,sort.

limit:用來獲取限定長度的內容.

query.limit(20); //只返回前20個內容

skip: 返回,跳過指定doc后的值.

query.skip(2);

sort: 用來設置根據指定字段排序. 可以設置為1:升序, -1:降序.

query.sort({name:1,age:-1});

實際上, 關于query,我們需要了解的也就差不多了.

document刪除

reomve操作僅在通過回調時執行。 要強制執行沒有回調,您必須先調用remove(),然后使用exec()方法執行它。
我們可以在document上執行remove方法也可以在Model上。

Model.find().remove({ name: 'Anne Murray' }, callback)
Model.remove({ name: 'Anne Murray' }, callback)
//沒有添加回調情況
Model.find().remove({ name: 'Anne Murray' }).remove(callback)
Model.remove({ name: 'Anne Murray' }).exce(callback)

document更新

使用Model.update([(conditions, doc, [options], [callback])]
不返回更新對象到應用程序。如果要更新數據庫中的單個文檔并將其返回到應用程序,請改用findOneAndUpdate。

參數說明:

  • conditions: 就是query. 通過query獲取到指定doc
  • doc: 就是用來替換doc內容的值.
  • options: 這塊需要說一些下.
    safe (boolean) 是否開啟安全模式 (default for true)
    upsert (boolean) 如果沒有匹配到內容,是否自動創建 ( default for false)
    multi (boolean) 如果有多個doc,匹配到,是否一起更改 ( default for false)
    strict (boolean) 使用嚴格模式(default for false)
    overwrite (boolean) 匹配到指定doc,是否覆蓋 (default for false)
    runValidators (boolean): 表示是否用來啟用驗證. 實際上,你首先需要寫一個驗證. 關于如果書寫,驗證大家可以參考下文, validate篇(default for false)

new(使用findOneAndUpdate時才有參數):bool - 如果為true,則返回修改后的文檔而不是原始文件。 默認為false。

Model.update({age:18}, { $set: { name: 'jason borne' }}, {multi:true}, function (err, raw) {
  if (err) return handleError(err);
  console.log('raw 就是mongodb返回的更改狀態的falg ', raw);
  //比如: { ok: 1, nModified: 2, n: 2 }
});

其中的$set是,用來指明更新的字段。

Validation

驗證器在SchemaType中定義。
Validation 是一種中間件,Mongoose 觸發 validation 同 a pre('save')鉤子一樣 。
你能夠手動觸發 validation 通過doc.validate(callback) or doc.validateSync()。

cat.save(function(error) {
//自動執行,validation
});

//手動觸發 validatio
//上面已經設置好user的字段內容.
  user.validate(function(error) {
    //error 就是驗證不通過返回的錯誤信息
     assert.equal(error.errors['phone'].message,
        '555.0123 is not a valid phone number!');
    });
});

內置驗證器

Mongoose 有一些列內置驗證器.

  • 所有的SchemaTypes都有required驗證器
  • min,max: 用來給Number類型的數據設置限制.
 var breakfastSchema = new Schema({
      eggs: {
        type: Number,
        min: [6, 'Too few eggs'],
        max: 12
      }
});
  • enum,match,maxlength,minlength: 這些驗證是給string類型的. enum 就是枚舉,表示該屬性值,只能出席那那些. match是用來匹配正則表達式的. maxlength&minlength 顯示字符串的長度.
new Schema({
    drink: {
        type: String,
        enum: ['Coffee', 'Tea']
      },
     food:{
        type: String,
        match:/^a/,
        maxlength:12,
        minlength:6
    }
})

自定義驗證器

如果內置驗證器不夠,您可以定義自定義驗證器以滿足您的需要。

// 創建驗證器
function validator (val) {
  return val == 'something';
}
new Schema({ name: { type: String, validate: validator }});

// 附帶自定義錯誤信息

var custom = [validator, 'Uh oh, {PATH} does not equal "something".']
new Schema({ name: { type: String, validate: custom }});

//添加多驗證器

var many = [
    { validator: validator, msg: 'uh oh' }
  , { validator: anotherValidator, msg: 'failed' }
]
new Schema({ name: { type: String, validate: many }});

// 直接通過SchemaType.validate方法定義驗證器:

var schema = new Schema({ name: 'string' });
schema.path('name').validate(validator, 'validation of `{PATH}` failed with value `{VALUE}`');

驗證錯誤對象

驗證失敗后返回的錯誤包含一個包含實際ValidatorError對象的錯誤對象。 每個ValidatorError都有kind,path,value和message屬性。

  var toySchema = new Schema({
      color: String,
      name: String
    });

    var Toy = db.model('Toy', toySchema);

    var validator = function (value) {
      return /blue|green|white|red|orange|periwinkle/i.test(value);
    };
    Toy.schema.path('color').validate(validator,
      'Color `{VALUE}` not valid', 'Invalid color');

    var toy = new Toy({ color: 'grease'});

    toy.save(function (err) {
      // err is our ValidationError object
      // err.errors.color is a ValidatorError object
      assert.equal(err.errors.color.message, 'Color `grease` not valid');
      assert.equal(err.errors.color.kind, 'Invalid color');
      assert.equal(err.errors.color.path, 'color');
      assert.equal(err.errors.color.value, 'grease');
      assert.equal(err.name, 'ValidationError');
    });

更新驗證器

在Model.update那一節有個參數–runValidators. 還沒有詳細說. 這里, 展開一下. 實際上, validate一般只會應用在save上, 如果你想在update使用的話, 需要額外的trick,而runValidators就是這個trick.
Mongoose還支持update()和findOneAndUpdate()操作的驗證。 在Mongoose 4.x中,更新驗證器默認關閉 - 您需要指定runValidators選項。

var opts = { runValidators: true };
    Test.update({}, update, opts, function(error) {  //額外開啟runValidators的驗證
      // There will never be a validation error here
    });

更多驗證器用法請參考官方文檔

population

ongodb 本來就是一門非關系型數據庫。 但有時候,我們又需要聯合其他的table進行數據查找。 mongoose提供的 population. 用來連接多表數據查詢. 一般而言, 我們只要提供某一個collection的_id , 就可以實現完美的聯合查詢. population 用到的關鍵字是: ref 用來指明外聯的數據庫的名字. 一般,我們需要在schema中就定義好.

var mongoose = require('mongoose')
  , Schema = mongoose.Schema
  
var personSchema = Schema({
  _id     : Number,
  name    : String,
  age     : Number,
  stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});

var storySchema = Schema({
  _creator : { type: Number, ref: 'Person' },
  title    : String,
  fans     : [{ type: Number, ref: 'Person' }]
});

var Story  = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);

Note: ObjectId, Number, String, and Buffer are valid for use as refs.

使用populate query方法進行關聯

Story
.findOne({ title: 'Once upon a timex.' })
.populate('_creator')
.exec(function (err, story) {
  if (err) return handleError(err);
  console.log('The creator is %s', story._creator.name);
  // prints "The creator is Aaron"
});

中間件

mongoose里的中間件,有兩個, 一個是pre, 一個是post.

  • pre: 在指定方法執行之前綁定。 中間件的狀態分為 parallel和series.
  • post: 相當于事件監聽的綁定

這里需要說明一下, 中間件一般僅僅只能限于在幾個方法中使用. (但感覺就已經是全部了)

  • doc 方法上: init,validate,save,remove;
  • model方法上: count,find,findOne,findOneAndRemove,findOneAndUpdate,update

pre

我們來看一下,pre中間件是如何綁定的.

串行

var schema = new Schema(..);
schema.pre('save', function(next) {
  // do stuff
  next(); //執行完畢,執行下一中間件
});

并行

var schema = new Schema(..);

// 設置第二參數為true,意味這是一個并行中間件
// as the second parameter if you want to use parallel middleware.
schema.pre('save', true, function(next, done) {
  // calling next kicks off the next middleware in parallel
  next();
  setTimeout(done, 100);
});

post

post會在指定事件后觸發,就像事件監聽器一樣,post鉤子沒什么控制流程,即它是異步的。

schema.post('save', function(doc) {
 //在save完成后 觸發.
  console.log('%s has been saved', doc._id);
});

當save方法調用時, 便會觸發post綁定的save事件.
假如你綁定了多個post。 也可以需要指定一下中間件順序.

// Takes 2 parameters: this is an asynchronous post hook
schema.post('save', function(doc, next) {
  setTimeout(function() {
    console.log('post1');
    // Kick off the second post hook
    next();
  }, 10);
});

// Will not execute until the first middleware calls `next()`
schema.post('save', function(doc, next) {
  console.log('post2');
  next();
})
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容