Mongoose Summary

八叉樹發表於2018-12-11

Mongoose裡面最重要的三個概念是Schemas,Models和Documents,另外還有驗證,中介軟體等,前面三個至關重要,這裡像填充Populate鑑別器Discriminators的相關知識就不介紹了,平時基本上不太能用到,具體的可以查閱官方文件。

Mongoose Summary

Mongoose的一切始於SchemaSchema定義collection裡的文件的構成,models是從Schema編譯來的建構函式,它們的例項就代表著可以從資料庫儲存和讀取的documents,從資料庫建立和讀取document的操作都是通過model進行的,而儲存時我們是在model例項上進行的。

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

var small = new Tank({ size: 'small' });
small.save(function (err) {
  if (err) return handleError(err);
  // saved!
})

// or

Tank.create({ size: 'small' }, function (err, small) {
  if (err) return handleError(err);
  // saved!
})
複製程式碼

模式Schemas

  1. 如何為models例項(Documents)定義我們自己的方法 雖然douments有很多自帶的例項方法,但我們也可以自定義我們自己的方法,這時需要在schema上定義方法。
  var animalSchema = new Schema({ name: String, type: String });

  animalSchema.methods.findSimilarTypes = function(cb) {
    return this.model('Animal').find({ type: this.type }, cb);
  };
  
  var Animal = mongoose.model('Animal', animalSchema);
  var dog = new Animal({ type: 'dog' });

  dog.findSimilarTypes(function(err, dogs) {
    console.log(dogs); // woof
  });
複製程式碼

也可以新增靜態方法

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

2.Schemas有很多的配置選項,可以在構造時或者直接set

new Schema({..}, options);

// or

var schema = new Schema({..});
schema.set(option, value);
複製程式碼

模型Models

查詢

查詢文件可以用model的find,findById,findOne和where這些靜態方法

刪除

remove方法

更新

model的update方法可以修改資料庫中的文件,不過不會把文件返回

findOneAndUpdate方法用來更新單獨一條文件並且返回給應用層

文件Documents

檢索

檢索方法較多,暫不闡述

更新

更新的方法也比較多

  • findById+改變值得操作

    Tank.findById(id, function (err, tank) {
      if (err) return handleError(err);
    
      tank.size = 'large';//或者使用tank.set({ size: 'large' });
      tank.save(function (err, updatedTank) {
        if (err) return handleError(err);
        res.send(updatedTank);
      });
    });
    複製程式碼
  • update

    Tank.update({ _id: id }, { $set: { size: 'large' }}, callback);
    複製程式碼
  • findByIdAndUpdate 這個方法會返回文件

    Tank.findByIdAndUpdate(id, { $set: { size: 'large' }}, { new: true }, function (err, tank) {
      if (err) return handleError(err);
      res.send(tank);
    });
    複製程式碼
  • 其他方法如findAndUpdate/Remove查詢並返回最多一個文件

驗證

Document會在被儲存之前驗證

覆蓋

可以用.set( )覆蓋整個文件

Tank.findById(id, function (err, tank) {
  if (err) return handleError(err);
  // Now `otherTank` is a copy of `tank`
  otherTank.set(tank);
});
複製程式碼

查詢queries

Model 的方法中包含查詢條件引數的( find findById count update )都可以按以下兩種方式執行:

  • 傳入 callback 引數,操作會被立即執行,查詢結果被傳給回撥函式( callback )。

  • 不傳 callback 引數,Query 的一個例項(一個 query 物件)被返回,這個 query 提供了構建查詢器的特殊介面。Query 例項有一個 .then() 函式,用法類似 promise。

    傳callback引數的情況:

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

// 查詢每個 last name 是 'Ghost' 的 person, select `name` 和 `occupation` 欄位
Person.findOne({ 'name.last': 'Ghost' }, 'name occupation', function (err, person) {
  if (err) return handleError(err);
  // Prints "Space Ghost is a talk show host".
  console.log('%s %s is a %s.', person.name.first, person.name.last,
    person.occupation);
});
複製程式碼

不傳callback引數的情況:

// 查詢每個 last name 是 'Ghost' 的 person
var query = Person.findOne({ 'name.last': 'Ghost' });

// select `name` 和 `occupation` 欄位
query.select('name occupation');

// 然後執行查詢
query.exec(function (err, person) {
  if (err) return handleError(err);
  // Prints "Space Ghost is a talk show host."
  console.log('%s %s is a %s.', person.name.first, person.name.last,
    person.occupation);
});
複製程式碼

模式型別SchemaTypes

有一下SchemaTypes:

  • String

  • Number

  • Date

  • Buffer

  • Boolean

  • Mixed

  • ObjectId

  • Array

  • Decimal128 可以宣告schema type為某一種type,或者賦值一個含有type屬性的物件

    var schema1 = new Schema({
      test: String // `test` is a path of type String
    });
    
    var schema2 = new Schema({
      test: { type: String } // `test` is a path of type string
    });
    複製程式碼

    除了type屬性,以下有一些全部type可用的選項和一些限定部分type使用的選項

    全部可用
    • required: 布林值或函式 如果值為真,為此屬性新增 required 驗證器
    • default: 任何值或函式 設定此路徑預設值。如果是函式,函式返回值為預設值
    • select: 布林值 指定 query 的預設 projections
    • validate: 函式 adds a validator function for this property
    • get: 函式 使用 Object.defineProperty() 定義自定義 getter
    • set: 函式 使用 Object.defineProperty() 定義自定義 setter
    • alias: 字串 僅mongoose >= 4.10.0。 為該欄位路徑定義虛擬值 gets/sets
    var numberSchema = new Schema({
      integerOnly: {
        type: Number,
        get: v => Math.round(v),
        set: v => Math.round(v),
        alias: 'i'
      }
    });
    
    var Number = mongoose.model('Number', numberSchema);
    
    var doc = new Number();
    doc.integerOnly = 2.001;
    doc.integerOnly; // 2
    doc.i; // 2
    doc.i = 3.001;
    doc.integerOnly; // 3
    doc.i; // 3
    複製程式碼
    索引相關
    • index: 布林值 是否對這個屬性建立索引
    • unique: 布林值 是否對這個屬性建立唯一索引
    • sparse: 布林值 是否對這個屬性建立稀疏索引
var schema2 = new Schema({
  test: {
    type: String,
    index: true,
    unique: true // Unique index. If you specify `unique: true`
    // specifying `index: true` is optional if you do `unique: true`
  }
});
複製程式碼
String
  • lowercase: 布林值 是否在儲存前對此值呼叫 .toLowerCase()
  • uppercase: 布林值 是否在儲存前對此值呼叫 .toUpperCase()
  • trim: 布林值 是否在儲存前對此值呼叫 .trim()
  • match: 正規表示式 建立驗證器檢查這個值是否匹配給定正規表示式
  • enum: 陣列 建立驗證器檢查這個值是否包含於給定陣列
Number
  • min: 數值 建立驗證器檢查屬性是否大於或等於該值
  • max: 數值 建立驗證器檢查屬性是否小於或等於該值
Date
  • min: Date
  • max: Date

驗證validation

  • 驗證定義於 SchemaType
  • 驗證是一個中介軟體。它預設作為 pre('save')` 鉤子註冊在 schema 上
  • 你可以使用 doc.validate(callback)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.');
    });
複製程式碼

內建validators

Mongoose有一些內建驗證器

自定義驗證器

自定義驗證器通過傳入一個檢驗函式來定義

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

    var User = db.model('user', userSchema);
    var user = new User();
    var error;

    user.phone = '555.0123';
    error = user.validateSync();
    assert.equal(error.errors['phone'].message,
      '555.0123 is not a valid phone number!');

    user.phone = '';
    error = user.validateSync();
    assert.equal(error.errors['phone'].message,
      'User phone number required');

    user.phone = '201-555-0123';
    // Validation succeeds! Phone number is defined
    // and fits `DDD-DDD-DDDD`
    error = user.validateSync();
    assert.equal(error, null);
複製程式碼

非同步自定義驗證器

自定義檢驗器可以是非同步的。如果檢驗函式 返回 promise (像 async 函式), mongoose 將會等待該 promise 完成。 如果你更喜歡使用回撥函式,設定 isAsync 選項, mongoose 會將回撥函式作為驗證函式的第二個引數。

var userSchema = new Schema({
      name: {
        type: String,
        // You can also make a validator async by returning a promise. If you
        // return a promise, do **not** specify the `isAsync` option.
        validate: function(v) {
          return new Promise(function(resolve, reject) {
            setTimeout(function() {
              resolve(false);
            }, 5);
          });
        }
      },
      phone: {
        type: String,
        validate: {
          isAsync: true,
          validator: function(v, cb) {
            setTimeout(function() {
              var phoneRegex = /\d{3}-\d{3}-\d{4}/;
              var msg = v + ' is not a valid phone number!';
              // 第一個引數是布林值,代表驗證結果
              // 第二個引數是報錯資訊
              cb(phoneRegex.test(v), msg);
            }, 5);
          },
          // 預設報錯資訊會被 `cb()` 第二個引數覆蓋
          message: 'Default error message'
        },
        required: [true, 'User phone number required']
      }
    });

    var User = db.model('User', userSchema);
    var user = new User();
    var error;

    user.phone = '555.0123';
    user.name = 'test';
    user.validate(function(error) {
      assert.ok(error);
      assert.equal(error.errors['phone'].message,
        '555.0123 is not a valid phone number!');
      assert.equal(error.errors['name'].message,
        'Validator failed for path `name` with value `test`');
    });
  
複製程式碼

中介軟體middleware

中介軟體(pre和post鉤子)是在非同步函式執行時函式傳入的控制函式,mongoose中所有的中介軟體都支援pre和post鉤子。

pre鉤子

pre鉤子分為序列和並行兩種,序列就是中介軟體一個接一個的執行,也就是上一個中介軟體呼叫next函式的時候,下一個執行。

var schema = new Schema(..);
schema.pre('save', function(next) {
  // do stuff
  next();
});
複製程式碼

並行中介軟體提供細粒度流控制

var schema = new Schema(..);

// 只有第二個引數為‘true'時才表示並行執行
schema.pre('save', true, function(next, done) {
  // calling next kicks off the next middleware in parallel
  next();
  setTimeout(done, 100);
});
複製程式碼

post中介軟體

post中介軟體在方法執行後呼叫

schema.post('init', function(doc) {
  console.log('%s has been initialized from the db', doc._id);
});
schema.post('validate', function(doc) {
  console.log('%s has been validated (but not saved yet)', doc._id);
});
schema.post('save', function(doc) {
  console.log('%s has been saved', doc._id);
});
schema.post('remove', function(doc) {
  console.log('%s has been removed', doc._id);
});
複製程式碼

如果在回撥函式傳入兩個引數,mongoose會認為第二個引數是next函式,我們可以通過next觸發下一個中介軟體

// 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();
});
複製程式碼

連線Connections

我們可用mongoose.connect()連線,最簡單的如mongoose.connect('mongodb://localhost/myapp');

,當然我們也可以在uri中指定多個引數mongoose.connect('mongodb://username:password@host:port/database?options...');

我們也可以用mongoose.createConnection()來連線,它返回一個新的連線,connection物件後面用於建立和檢索models。

const conn = mongoose.createConnection('mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]', options);
複製程式碼

操作快取

意思就是我們不必等待連線建立成功就可以使用models,mongoose會先快取model操作

var MyModel = mongoose.model('Test', new Schema({ name: String }));
// 連線成功前操作會被掛起
MyModel.findOne(function(error, result) { /* ... */ });

setTimeout(function() {
  mongoose.connect('mongodb://localhost/myapp');
}, 60000);
複製程式碼

如果要禁用快取,可修改bufferCommands配置,也可以全域性禁用bufferCommands

mongoose.set('bufferCommands', false);
複製程式碼

選項

connect方法可以接受options引數,具體有哪些引數可以參考官方文件

mongoose.connect(uri,options)
複製程式碼
const options = {
  useMongoClient: true,
  autoIndex: false, // Don't build indexes
  reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
  reconnectInterval: 500, // Reconnect every 500ms
  poolSize: 10, // Maintain up to 10 socket connections
  // If not connected, return errors immediately rather than waiting for reconnect
  bufferMaxEntries: 0
};
mongoose.connect(uri, options);
複製程式碼

回撥

connect函式可以接受回撥函式或者返回一個promise

mongoose.connect(uri,options,function(error){
})

mongoose.connect(uri,options).then(
 () => { /** ready to use. The `mongoose.connect()` promise resolves to undefined. */ },
  err => { /** handle initial connection error */ }
)
複製程式碼

注意

這裡有個連線池的概念,無論是使用 mongoose.connect 或是 mongoose.createConnection 建立的連線, 都被納入預設最大為 5 的連線池,可以通過 poolSize 選項調整:

// With object options
mongoose.createConnection(uri, { poolSize: 4 });

const uri = 'mongodb://localhost/test?poolSize=4';
mongoose.createConnection(uri);
複製程式碼

子文件Subdocument

子文件指的是巢狀在另一個文件中的文件,mongoose文件有兩種不同的概念:子文件陣列和單個巢狀子文件

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

var parentSchema = new Schema({
  // Array of subdocuments
  children: [childSchema],
  // Single nested subdocuments. Caveat: single nested subdocs only work
  // in mongoose >= 4.2.0
  child: childSchema
});
複製程式碼

子文件和普通文件類似,主要不同的是子文件不能單獨儲存,它們會在它們的頂級文件儲存時儲存

var Parent = mongoose.model('Parent', parentSchema);
var parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] })
parent.children[0].name = 'Matthew';

// `parent.children[0].save()` 無操作,雖然他觸發了中介軟體
// 但是**沒有**儲存文件。你需要 save 他的父文件
parent.save(callback);
複製程式碼

常用的操作:

  1. Model.find Model.find(query,fields,options,callback)//fields和options都是可選引數 簡單查詢 Model.find({'csser.com':5},function(err,docs){docs是查詢的結果陣列}); 只查詢指定鍵的結果 Model.find({},['first','last'],function(err,docs){//docs此時只包含文件的部分鍵值})

  2. Model.findOne 它只返回單個文件 Model.findOne({age:5},function(err,doc){//doc是單個文件});

  3. Model.findById 與findOne相同,但它接收文件的_id作為引數,返回單個文件。_id可以是字串或者ObjectID物件 Model.findById(obj._id,function(err,doc){//doc是單個文件})

  4. Model.count 返回符合條件的文件數 Model.count(conditions,callback)

  5. Model.remove 刪除符合條件的文件 Model.remove(conditions,callback)

  6. Model.distinct 查詢符合條件的文件並返回根據鍵分組的結果

    Model.distinct(field,conditions,callback)

  7. Model.where 當查詢比較複雜的時候就用where Model.where('age').gte(25)

    where('tags').in(['movie','music','art']) .select('name','age','tags') .skip(20) .limit(10) .asc('age') .slaveOk() .hint({age:1,name:1}) .run(callback)

  8. Model.$where

    有時我們需要在MongoDB中使用JavaScript表示式進行查詢,這時可以使用find({ where:javascript})方式,where是一種快捷方式,並支援鏈式呼叫查詢 Model.$where('this.firstname===this.lastname').exec(callback)

  9. Model.update

    使用update子句更新指定條件的文件,更新資料在傳送到資料庫伺服器之前會改變模型的型別,注意update返回的是被修改的文件數量 var conditions={name:'borne'} ,update={$inc:{visits:1}} ,options={multi:true} Model.update(conditions,update,options,callback)

    注意:為了向後相容,所有頂級更新鍵如果不是原子操作命名的,會被統一按$set操作處理,例如:

    var queryb={name:'borne'};

    Model.update(query,{name:'jason borne'},options,callback)

    上面會被這樣傳送到資料庫伺服器的

    Model.update(query,{$set:{name:'jason borne'}},options,callback)

  10. 查詢API 如果不提供回撥函式,所有這些方法都返回Query物件,它們都可以被再次修改,直到呼叫exec方法 var query=Model.find({});

    query.where('field',5);

    query.limit(5);

    query.skip(100);

相關文章