Schemas

定義一個(gè) Schema

Mongoose工作都有開始于Schema。每個(gè)Schema對(duì)應(yīng)(映射)一個(gè)mongodb 的表(集合)并且可以定文義該表的字段格式;

  var mongoose = require('mongoose');
  var Schema = mongoose.Schema;
  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
    }
  });

如果你創(chuàng)建了Schema后,又想添加字段,可以使用Schema的add方法
代碼中定義的每個(gè)字段屬性都會(huì)轉(zhuǎn)換成SchemaType.例如我們定了title字段,它將被轉(zhuǎn)換成String 的SchemaType 并且字段date將換成Date SchemaType
字段的設(shè)置可以嵌套,像JOSN格式一樣,比如meta字段

允許定的SchemaTypes 有:

查看更多SchemaTypes 點(diǎn)擊這里.
Schemas不僅可以定義表的結(jié)構(gòu)和屬性,還能定義了文檔實(shí)例方法、靜態(tài)模型方法、復(fù)合索引中間件.。

創(chuàng)建Model

創(chuàng)建一個(gè)Model,我們必須要用到Schema,我們使用上面的blogSchema 創(chuàng)建一個(gè)Model,使用mongoose.model(modelName,blogSchema)

var Blog = mongoose.model('Blog', blogSchema);
  // 繼續(xù)……

實(shí)例方法

Model是一個(gè)文檔結(jié)構(gòu),有許多內(nèi)置的方法。我們也可以自定義實(shí)例方法

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

  //給animalSchema 添一個(gè)方法
  animalSchema.methods.findSimilarTypes = function(cb) {
    return this.model('Animal').find({ type: this.type }, cb);
  };

現(xiàn)在所有animalSchema的實(shí)例都有一個(gè)findSimilarTypes的方法

 var Animal = mongoose.model('Animal', animalSchema);
 var dog = new Animal({ type: 'dog' });

  dog.findSimilarTypes(function(err, dogs) {
    console.log(dogs); //查找 type是dog的表
  });
  • 如果重寫Mongoose的文檔內(nèi)置方法可能會(huì)造成不可預(yù)料的錯(cuò),點(diǎn)擊這里查看更多詳情

  • 上面的實(shí)例使用了Schema.methods對(duì)創(chuàng)建保存一個(gè)實(shí)例方法,點(diǎn)擊這里查看Schema.method()的詳情細(xì)介紹

  • 不要使用es6的箭頭方法,關(guān)于es6簡(jiǎn)頭方法的使用請(qǐng)自行查找

靜態(tài)方法

向model添加一個(gè)靜態(tài)方法也非常簡(jiǎn)單,我們繼使用animalSchema:

  // 添加靜態(tài)方法
  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);
  });

查詢助手

你可以定義查詢助手方法,類擬實(shí)例方法,但是主要是用于mongoose的查詢。查詢助手方法允許可以鏈?zhǔn)绞褂?例如:

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

  var Animal = mongoose.model('Animal', animalSchema);

  Animal.find().byName('fido').exec(function(err, animals) {
    console.log(animals);
  });

  Animal.findOne().byName('fido').exec(function(err, animal) {
    console.log(animal);
  });

索引

MongoDB 支持二級(jí)索引. 我們可以使用mongoose在Schema 定義這些索引,可以使用以下兩種方法:

//在Schema直接定義
var animalSchema = new Schema({
    name: String,
    type: String,
    tags: { type: [String], index: true }
  });

//在Schema對(duì)象方法中添加  
animalSchema.index({ name: 1, type: -1 });

當(dāng)你的應(yīng)用開始運(yùn)行時(shí),Mongoose會(huì)為Schema中定義的每一個(gè)索引自動(dòng)創(chuàng)建索引,這個(gè)創(chuàng)建是依次的進(jìn)行的,不是同時(shí)的時(shí)的。當(dāng)所有索引創(chuàng)建完成時(shí)或出錯(cuò)誤會(huì)在model上發(fā)送一個(gè)Index事件。雖然對(duì)開發(fā)者來說創(chuàng)建索引有利于開發(fā),但是生產(chǎn)建不要使用索引,因?yàn)闀?huì)對(duì)性能造成影響??梢越眯┕δ?。使用如下代碼

 mongoose.connect('mongodb://user:pass@localhost:port/database', { autoIndex: false });
  // or
  mongoose.createConnection('mongodb://user:pass@localhost:port/database', { autoIndex: false });
  // or
  animalSchema.set('autoIndex', false);
  // or
  new Schema({..}, { autoIndex: false });

開發(fā)時(shí)使用索引,可以使用以下方式監(jiān)聽I(yíng)ndex事件


  animalSchema.index({ _id: 1 }, { sparse: true });
  var Animal = mongoose.model('Animal', animalSchema);

  Animal.on('index', function(error) {
    console.log(error.message);
  });

詳細(xì)請(qǐng)查看 Model#ensureIndexes 方法。

虛擬(Virtuals)

Virtuals能獲取和設(shè)置的文檔屬性,但不能持久化到MongoDB。getter用于格式化或組合字段,而setter用于將單個(gè)值分解為多個(gè)值進(jìn)行存儲(chǔ)。

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

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

  var axl = new Person({
    name: { first: 'Axl', last: 'Rose' }
  });

你可以這樣使用屬性

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

每次使用上面將name.firstname.last鏈接起來非常麻煩,如果你想對(duì)name做一些特殊的處理,你可以使用Virtuals的get來處理。虛擬屬性的getter允許您定義一個(gè)不會(huì)持久化到MongoDB的fullName屬性。

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

//使用時(shí),跟es6的get set有點(diǎn)相似
console.log(axl.fullName); 

如果你使用toJSON()toObject()時(shí),mongoose默認(rèn)沒有包含虛擬屬性的;這包括在Mongoose文檔上調(diào)用JSON.stringify()的輸出,因?yàn)?code>JSON.stringify()調(diào)用toJSON()。將{virtuals: true}傳遞給toObject()toJSON()。

你還可以為fullName設(shè)置一個(gè)set方法

personSchema.virtual('fullName').
  get(function() { return this.name.first + ' ' + this.name.last; }).
  set(function(v) {
    this.name.first = v.substr(0, v.indexOf(' '));
    this.name.last = v.substr(v.indexOf(' ') + 1);
  });

axl.fullName = 'William Rose'; // `axl.name.first` 是 "William"

在其他驗(yàn)證之前應(yīng)用虛擬屬性設(shè)置器。因此,即使需要姓和名字段,上面的示例仍然有效。虛擬屬性不能作為字段查詢條件,因?yàn)閿?shù)據(jù)庫不存在該字段的

別名(Aliases)

別名是一種特殊的虛擬類型,getter和setter在其中無縫地獲取和設(shè)置另一個(gè)屬性。這對(duì)于節(jié)省網(wǎng)絡(luò)帶寬非常方便,因此可以將存儲(chǔ)在數(shù)據(jù)庫中的短屬性名稱轉(zhuǎn)換為更長(zhǎng)的名稱,以提高代碼的可讀性。

var personSchema = new Schema({
  n: {
    type: String,
    // Now accessing `name` will get you the value of `n`, and setting `n` will set the value of `name`
    alias: 'name'
  }
});

// Setting `name` will propagate to `n`
var person = new Person({ name: 'Val' });
console.log(person); // { n: 'Val' }
console.log(person.toObject({ virtuals: true })); // { n: 'Val', name: 'Val' }
console.log(person.name); // "Val"

person.name = 'Not Val';
console.log(person); // { n: 'Not Val' }

還可以在嵌套路徑上聲明別名。使用嵌套模式和子文檔更容易,但是也可以內(nèi)聯(lián)聲明嵌套路徑別名,只要使用完整的嵌套路徑嵌套即可

const childSchema = new Schema({
  n: {
    type: String,
    alias: 'name'
  }
}, { _id: false });

const parentSchema = new Schema({
  // If in a child schema, alias doesn't need to include the full nested path
  c: childSchema,
  name: {
    f: {
      type: String,
      // Alias needs to include the full nested path if declared inline
      alias: 'name.first'
    }
  }
});

選項(xiàng)

Schemas中有些一些配置選項(xiàng),可直傳給構(gòu)造方法,也可以用set設(shè)置,如下:

new Schema({..}, options);

// 或

var schema = new Schema({..});
schema.set(option, value);

可設(shè)置的選項(xiàng)目有:

option: autoIndex

這個(gè)是否創(chuàng)建索引,默認(rèn)情況下會(huì)開啟這一選項(xiàng),在開發(fā)和測(cè)試的時(shí)候還是很有用的。但在生產(chǎn)會(huì)產(chǎn)生比較大的負(fù)載,生產(chǎn)上建議關(guān)閉此選項(xiàng),關(guān)閉方式如下:

const schema = new Schema({..}, { autoIndex: false });
const Clock = mongoose.model('Clock', schema);
Clock.ensureIndexes(callback);

這個(gè)選項(xiàng)默認(rèn)是開啟的,你可以關(guān)閉,使用mongoose.use('autoIndex', false);

option: autoCreate

在Mongoose構(gòu)建索引之前,如果autoCreate設(shè)置為true,它調(diào)用Model.createCollection()在MongoDB中創(chuàng)建底層集合。調(diào)用createCollection()根據(jù)collation選項(xiàng)設(shè)置集合的默認(rèn)排序規(guī)則,如果設(shè)置了capped模式選項(xiàng),則將表建立為一個(gè)有上限的表。與autoIndex一樣,將autoCreate設(shè)置為true有助于開發(fā)和測(cè)試環(huán)境。
不幸的是,createCollection()不能更改現(xiàn)有集合。例如,如果您將capped: 1024添加到您的模式中,而現(xiàn)有的集合沒有被覆蓋,createCollection()將拋出一個(gè)錯(cuò)誤。通常,對(duì)于生產(chǎn)環(huán)境,autoCreate應(yīng)該為false。

option: bufferCommands

這個(gè)選項(xiàng)主是當(dāng)mongoose 中斷了鏈接會(huì)自動(dòng)重新鏈接,直到驅(qū)動(dòng)程序重新連接為止,禁用請(qǐng)?jiān)O(shè)置為false

var schema = new Schema({..}, { bufferCommands: false });
//全局設(shè)置
mongoose.set('bufferCommands', true);

option: capped

設(shè)置Mongodb表大小,單字為字節(jié)

new Schema({..}, { capped: 1024 });

如查你要對(duì)該屬性,設(shè)置maxautoIndexId屬性,那么你可以傳一個(gè)對(duì)象,但該對(duì)象一定要包含size屬性

new Schema({..}, { capped: { size: 1024, max: 1000, autoIndexId: true } });

option: collection

Mongodb表名,默認(rèn)會(huì)生一個(gè)表名,但是最后此項(xiàng)不要用默認(rèn),最好給表設(shè)個(gè)名字

var dataSchema = new Schema({..}, { collection: 'data' });

option: id

是否給Schema添加一個(gè)虛擬id

// 默認(rèn)情況下
var schema = new Schema({ name: String });
var Page = mongoose.model('Page', schema);
var p = new Page({ name: 'mongodb.org' });
console.log(p); // { _id: '50341373e894ad16347efe01', name: 'mongodb.org' }

// 禁用_id
var childSchema = new Schema({ name: String }, { _id: false });
var parentSchema = new Schema({ children: [childSchema] });

var Model = mongoose.model('Model', parentSchema);

Model.create({ children: [{ name: 'Luke' }] }, function(error, doc) {
  // doc.children[0]._id will be undefined
});

可存數(shù)據(jù)的時(shí)候沒有_id是沒無保存數(shù)據(jù)的,最好不要禁用此功能

option: minimize

是否刪除空對(duì)象,以此來減小表所占的大小

const schema = new Schema({ name: String, inventory: {} });
const Character = mongoose.model('Character', schema);

// 如果“inventory”字段不是空的,將會(huì)存儲(chǔ)它
const frodo = new Character({ name: 'Frodo', inventory: { ringOfPower: 1 }});
await frodo.save();
let doc = await Character.findOne({ name: 'Frodo' }).lean();
doc.inventory; // { ringOfPower: 1 }

// 如果“inventory”字段是空的,將不會(huì)存儲(chǔ)它
const sam = new Character({ name: 'Sam', inventory: {}});
await sam.save();
doc = await Character.findOne({ name: 'Sam' }).lean();
doc.inventory; // undefined

可以通過以下代碼設(shè)置minimize屬

const schema = new Schema({ name: String, inventory: {} }, { minimize: false });
const Character = mongoose.model('Character', schema);

// 如果nventory是空對(duì)象時(shí),會(huì)存?zhèn)€空對(duì)象進(jìn)去
const sam = new Character({ name: 'Sam', inventory: {} });
await sam.save();
doc = await Character.findOne({ name: 'Sam' }).lean();
doc.inventory; // {}

判斷對(duì)象是否是空對(duì)象可以使用$isEmpty()

const sam = new Character({ name: 'Sam', inventory: {} });
sam.$isEmpty('inventory'); // true

sam.inventory.barrowBlade = 1;
sam.$isEmpty('inventory'); // false

option: read(暫時(shí)還不是很明白此項(xiàng)目的作用,后期弄懂再補(bǔ))

這個(gè)選項(xiàng)是給Schema起一個(gè)別名,配合### readConcern()使用

var schema = new Schema({..}, { read: 'primary' });            // 可以用別名 'p'
var schema = new Schema({..}, { read: 'primaryPreferred' });   // 可以用別名 'pp'
var schema = new Schema({..}, { read: 'secondary' });          // 可以用別名 's'
var schema = new Schema({..}, { read: 'secondaryPreferred' }); // 可以用別名 'sp'
var schema = new Schema({..}, { read: 'nearest' });            // 可以用別名 'n'

option: writeConcern

設(shè)置mongodb寫入策略(WriteConcern),具體說明請(qǐng)自行查找mongodb

const schema = new Schema({ name: String }, {
  writeConcern: {
    w: 'majority',
    j: true,
    wtimeout: 1000
  }
});

option: safe

safe是一個(gè)與writeConcern類似的遺留選項(xiàng)。特別是,safe: false是未確認(rèn)的寫入writeConcern: {w: 0}的縮寫。而是使用writeConcern選項(xiàng)。

option: shardKey

設(shè)置分片的時(shí)候用到;分片必須自行配置,詳情請(qǐng)自行查看mongodb文檔

new Schema({ .. }, { shardKey: { tag: 1, name: 1 }})

option: strict(嚴(yán)格模式)

這個(gè)選是默認(rèn)開啟的,開啟這個(gè)選項(xiàng),如果你的Schema沒有這個(gè)指定的字段,那么mongoose將不保存到數(shù)據(jù)庫中(建議開啟這個(gè)選項(xiàng))

var thingSchema = new Schema({..})
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // 如查開啟將不會(huì)保存到數(shù)據(jù)庫

// set to false..
var thingSchema = new Schema({..}, { strict: false });
var thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // 關(guān)閉會(huì)保存數(shù)據(jù)到數(shù)據(jù)庫

也可以通過以下方式開啟/關(guān)閉strict

var Thing = mongoose.model('Thing');
var thing = new Thing(doc, true);  // 第二個(gè)參數(shù)就strict的值,strict設(shè)為true
var thing = new Thing(doc, false); // 第二個(gè)參數(shù)就strict的值,strict設(shè)為false

可以將“strict”設(shè)置為“throw”,拋出錯(cuò)誤會(huì),而不是過濾沒有的數(shù)據(jù)
*注意,如查實(shí)例不存在的話,你怎么樣設(shè)置這個(gè)參數(shù)都是不起作用的

var thingSchema = new Schema({..})
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing;
thing.iAmNotInTheSchema = true;
thing.save(); // iAmNotInTheSchema  數(shù)據(jù)不會(huì)被保存

字段不向后兼容,strict選項(xiàng)不適用查詢時(shí)過濾在Schema不存在的數(shù)據(jù)

const mySchema = new Schema({ field: Number }, { strict: true });
const MyModel = mongoose.model('Test', mySchema);

// mongoose不會(huì)過濾notInSchema,因?yàn)閟trict對(duì)于查詢是不起作用的
MyModel.find({ notInSchema: 1 });

strict選項(xiàng)支持更新數(shù)據(jù)

MyModel.updateMany({}, { $set: { notInSchema: 1 } });

option: strictQuery

該字段與上文的strict相對(duì)應(yīng),這個(gè)字段是在查詢的時(shí)候過濾Schema沒有的字段查詢數(shù)據(jù)

const mySchema = new Schema({ field: Number }, {
  strict: true,
  strictQuery: true // 打開查詢過濾模式(嚴(yán)格模式)
});
const MyModel = mongoose.model('Test', mySchema);

// Mongoose會(huì)過濾掉'{ notInSchema: 1 }'因已經(jīng)打開查詢過濾模式,此條件無效
MyModel.find({ notInSchema: 1 });

option: toJSON

與toObject選項(xiàng)完全相同,但僅在調(diào)用documents 對(duì)象的toJSON方法時(shí)才有用(實(shí)話說,沒什么大卵用)

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' }
// 只要js對(duì)象被轉(zhuǎn)為字符串,就會(huì)調(diào)用toJSON
console.log(JSON.stringify(m)); // { "_id": "504e0cd7dd992d9be2f20b6f", "name": "Max Headroom is my name" }

option: toObject

documents 有一個(gè)toObject方法,它將mongoose文檔轉(zhuǎn)換為一個(gè)普通的javascript對(duì)象。此方法接受幾個(gè)選項(xiàng)。我們可以在這里聲明這些選項(xiàng),并在缺省情況下將其應(yīng)用于所有這些模式文檔,而不是根據(jù)每個(gè)文檔應(yīng)用這些選項(xiàng)
要讓所有virtuals對(duì)象都顯示在console.log輸出中,請(qǐng)將toObject選項(xiàng)設(shè)置為{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' }

option: typeKey

在默認(rèn)的情況下,如果你在Schema設(shè)置了一個(gè)字段,mongoose將認(rèn)為你這是類型聲明

// Mongoose 將認(rèn)為loc是一個(gè)字符串
var schema = new Schema({ loc: { type: String, coordinates: [Number] } });

如果你需要type是一個(gè)字段,而不是作為字段聲明的類型,那么你可以設(shè)置這個(gè)參數(shù)

var schema = new Schema({
  // Mongoose將認(rèn)為loc是一個(gè)對(duì)象,而不是一個(gè)字符串
  loc: { type: String, coordinates: [Number] },
  // Mongoose將認(rèn)為name是一個(gè)字符串,而不是一個(gè)對(duì)象
  name: { $type: String }
}, { typeKey: '$type' }); // 用$type替換type

option: validateBeforeSave

默認(rèn)情況下,文檔在保存到數(shù)據(jù)庫之前會(huì)自動(dòng)驗(yàn)證。這是為了防止保存無效的數(shù)據(jù)。如果希望手動(dòng)處理驗(yàn)證,并能夠保存沒有通過驗(yàn)證的數(shù)據(jù),可以將validatebeforeave設(shè)置為false。

var schema = new Schema({ name: String });
schema.set('validateBeforeSave', false);
schema.path('name').validate(function (value) {
    return v != null;
});
var M = mongoose.model('Person', schema);
var m = new M({ name: null });
m.validate(function(err) {
    console.log(err); // 不允許為空
});
m.save(); // 成功保存

option: versionKey

versionKey,在你最開始創(chuàng)建文檔插入數(shù)據(jù)時(shí)已經(jīng)創(chuàng)建了,一個(gè)記更改,添加的版本號(hào),默認(rèn)的字段是__v,如果字段跟你的業(yè)務(wù)邏輯有沖突,你可以像下面一樣寫

const schema = new Schema({ name: 'string' });
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'mongoose v3' });
await thing.save(); // { __v: 0, name: 'mongoose v3' }

// customized versionKey
new Schema({..}, { versionKey: '_somethingElse' })
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'mongoose v3' });
thing.save(); // { _somethingElse: 0, name: 'mongoose v3' }

版本號(hào)控制不是一個(gè)很可靠的解決并發(fā)的方案.
Note that Mongoose versioning is not a full optimistic concurrency solution. Use mongoose-update-if-currentfor OCC support. Mongoose versioning only operates on arrays:

還沒有寫完,待更新……

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容