mongoose学习笔记2之Schemas

指南

假设以下代码都运行在

let mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test');
let db = mongoose.connection;
db.on('error', () => {
  console.error('链接失败');
});
db.once('open', function () {
  //下面讲到的所有代码都在这里运行
});

Schema

定义 schema

在Mongoose中,任何事情都是从Schema(模式)开始的。每一个模式映射到MongoDB集合,并且定义了这个集合中文档的模型。

let Schema = mongoose.Schema;
let blogSchema = new Schema({
    title: String,
    author: String,
    body: String,
    comments: [{
        body: String,
        data: Date
    }],
    date: {
        type: Date, default: Date.now
    },
    hidden: Boolean,
    meta: {
        votes: Number,
        favs: Number
    }
});

在blogSchema中,我们定义的每一个key都被关联了一个SchemaType。比如title这个key,对应一个String SchemaType。date对应一个Date SchemaType。当然,这些key也可以对应一个对象,进行更深度的嵌套。

SchemaTypes 有以下几种:

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

Schema 不仅定义了你的文档结构和属性,而且也定义了文档的实例方法,静态的模型方法、复合索引和被称为中间件的文档生命周期钩子。


创建模型

要使用我们的模式定义,我们需要将我们的 blogSchema转换为我们可以使用的模型。为此,我们要将blogSchema传入 mongoose.model(modelName,schema):

var Blog = mongoose.model('Blog',blogSchema);

实例方法

模型的实例叫做文档。文档有许多自己的内置实例方法。我们也可以定义我们自己的自己的文档实例方法。

 let Schema = mongoose.Schema;
//定义一个schema
let animalSchema = new Schema({
    name: String,
    type: String
});
//为我们的animalSchema的methods对象分配一个函数。
animalSchema.methods.findSimilarTypes = function (cb) {
    console.log(this.model);
    return this.model('Animal').find({ type: this.type }, cb);
}
//现在,我们的所有animal实例都有一个findSimilarTypes方法可以使用。
let Animal = mongoose.model('Animal', animalSchema);
let dog = new Animal({
    type: 'dog'
});
dog.save(function (err, dog) {
    dog.findSimilarTypes(function (err, dogs) {
        console.log(dogs);
    });
});

静态方法

添加静态方法给一个model也同样简单。继续我们的animalSchema

let Schema = mongoose.Schema;
let animalSchema = new Schema({
    name: String,
    type: String
});
//给我们animalsChema上的statics对象添加一个方法。
animalSchema.statics.findByName = function(name,cb){
    this.find({name:new RegExp(name,'i')},cb);
}
let Animal = mongoose.model('Animal',animalSchema);
Animal.findByName('fido',function(err,target){
    console.log(target)
});

索引

MongoDB支持二级索引。 使用mongoose,我们在路径级别或模式级别的模式中定义这些索引。 在创建复合索引时,必须在模式级别定义索引。
建议在生产中禁用此行为,因为索引创建可能会导致显着的性能影响。 通过将模式的autoIndex选项设置为false来禁用该行为。

animalSchema.set('autoIndex', false);
// or
new Schema({..}, { autoIndex: false });

虚拟

虚拟是指可以获取和设置但不会持久保存到MongoDB的文档属性。 getters对于格式化或组合字段很有用,而settings对于将单个值拆分为多个值以用于存储是有用的。

let Schema = mongoose.Schema;
//定义一个Schema
let personSchema = new Schema({
    name:{
        first:String,
        last:String
    }
});
 //编译成model
let Person = mongoose.model('Person',personSchema);
//  创建一个文档
let bad = new Person({
    name:{
       first :'Walter',
        last:'White'
      }
});

假设我们想要记录bad的全名,那么我们可以手动这样处理。

  console.log(bad.name.first+' '+bad.name.last)

或者,我们可以在personSchema上定义一个虚拟属性getter,那么我们不需要每次都写出这个字符串连接代码。

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

现在,当我们访问我们的虚拟属性“name.full"时,我们的getter函数就会被调用,并且会将结果返回。

console.log('%s is insane', bad.name.full); // Walter White is insane

请注意,如果结果记录被转换为对象或JSON,默认情况下不会包括虚拟。将virtuals:true传递给toOject()或传递给toJSON()以使它们返回。
我们同样可以通过设置this.name.full来设置this.name.first和this.name.last。例如,如果我们想把bad的name.first和name.last分别改为"Breaking"和“bad",那么只需这样:

bad.name.full = 'Breaking Bad';

Mongoose允许你通过它的虚拟属性setters来做到这一点。

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

let Person = mongoose.model('Person',personSchema);
//虚拟属性getters
personSchema.virtual('name.full').get(function(){
        return this.name.first+' '+this.name.last;
});
//虚拟属性setters
personSchema.virtual('name.full').set(function(name){
    let split = name.split(' ');
    this.name.first = split[0];
    this.name.last = split[1];
});
let bad = new Person({
    name:{
       first :'Walter',
    last:'White'
    }
});
console.log(bad.name.full);  //Walter White
bad.name.full = 'Breaking Bad';
console.log(bad.name.full);  // Breaking Bad

Options


Schema 有一些可配置的选项,可以直接传递给构造函数或者直接设置:

new Schema({..},options);
//或者
let schema = new Schema({..});
schema.set(option,value);

有效的选项(options):

  • autoIndex
  • capped
  • collection
  • id
  • _id
  • read
  • safe
  • shardKey
  • strict
  • toJSON
  • toObject
  • versionKey

option:autoIndex

在应用程序启动时,Mongoose为您的Schema中声明的每个索引发送一个ensureIndex命令。 从Mongoose v3开始,默认情况下在后台创建索引。 如果要禁用自动创建功能并在创建索引时手动处理,请将模式autoIndex选项设置为false,并在模型上使用ensureIndexes方法。

 let Schema = mongoose.Schema;
 let schema = mongoose.Schema({
    name: String
 }, {
        autoIndex: false
    });
 let Person = mongoose.model('Person', schema);
 Person.ensureIndexes(function (err) {
    if (err) {
        console.error(err);
    }
 });

注意:不建议在生产环境中运行。 索引创建可能会影响数据库性能,具体取决于负载。 小心使用。
ensureIndex命令不是并行发送的。 这是为了避免MongoError:无法添加索引与后台操作进行中错误。


option: capped

Mongoose支持MongoDB限制集合大小。 要指定底层MongoDB集合的上限,请将capped选项设置为集合的最大大小(以字节为单位)。

let Schema = mongoose.Schema;
let schema = mongoose.Schema({
    name: String
}, {
        capped:1 //设置大小1字节
    });
let Person = mongoose.model('Person', schema);
let man = new Person({
    name:'noshower' 
});
man.save(function(err,target){
    if(err){
        console.error(err);
    }else{
        console.log(target);
    }
});

此时man能正常保存在数据库。但是,当name的值设置为很多字时,保存就会报错。

MongoError: object to insert exceeds cappedMaxSize

如果要传递其他选项(如max或auto IndexId),则capped选项也可以设置为对象。在这种情况下,你必须明确地传递其所需的大小选项。

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

option: collection

Mongoose默认情况下通过将模型名称传递给utils.toCollectionName方法来生成集合名称。 如果您需要为集合使用不同的名称,请设置此选项。

上面我们model的名称是Person,Mongoose自动给我们生成了people结合。
现在我们修改代码如下:

let schema = new Schema({name:String},{collection:'data'});

那么,生成的集合名字就叫"data";


option:id

Mongoose默认为每个schema分配一个id虚拟getter,它返回的文档_id字段会被强制转换为字符串,或者在ObjectIds及其hexString的情况下。 如果不想将id getter添加到模式中,可以在schema构建时禁用它传递此选项。

//默认行为
let Schema = mongoose.Schema;
let schema = new Schema({
    name:String
});
let Person = mongoose.model('Page',schema);
let man = new Person({
    name:'noshower'
});
console.log(man.id);  //5877026e2c52b034810535e2
// 禁用id
let Schema = mongoose.Schema;
let schema = new Schema({
    name:String
},{
    id:false
});
let Person = mongoose.model('Page',schema);
let man = new Person({
    name:'noshower'
});
console.log(man.id); //undefined

option: _id

Mongoose默认为每个模式分配一个_id字段,如果没有传递给Schema构造函数。 类型被设置为一个ObjectId,与MongoDB的默认行为一致。 如果您不想将_id添加到schema中,您可以使用此选项禁用它。

let Schema = mongoose.Schema;
let schema = new Schema({
    name:String
});
//默认行为
let Person = mongoose.model('Person',schema);
let man = new Person({
    name:'noshower'
});
console.log(man); //name: 'noshower', _id: 58771d4c27cb0d3869a88885 }

//禁止_id
let Schema = mongoose.Schema;
let schema = new Schema({
    name:String
},{
    _id:false
});
let Person = mongoose.model('Person',schema);
let man = new Person({
    name:'noshower'
});
console.log(man);//{ name: 'noshower' }
man.save(function(err,target){
    if(err){
        console.error(err);
    }else{
        console.log(target);
    }
});
//此时不能保存信息

Error: document must have an _id before saving
我们刚刚禁止了_id,但是在保存前,必须要有_id属性。那么,我们就需要在定义schema时,就定义好_id

let schema = new Schema({
  name:String,
  _id:Number
},{
    _id:false
});
let Person = mongoose.model('Person',schema);
let man = new Person({
    name:'noshower',
    _id:1
});
//此时能够正常保存了。
man.save(function(err,target){
    if(err){
        console.error(err);
    }else{
        console.log(target);//{ __v: 0, name: 'noshower', _id: 1 }
    }
});

option:safe

这个选项被传递给MongoDB和所有操作,并指定是否应该将错误返回给我们的回调,以及调整写入行为。

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

默认情况下,safe设置的是true。 通过设置safe,比如{j:1,w:2,wtimeout:10000}这样的东西,我们可以保证,写入被提交到MongoDB日志(j:1),至少有2个副本(w:2) 如果超过10秒(wtimeout:10000),写入将超时。 错误仍然会传递给我们的回调。

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

option:shardKey

当我们有一个分片的MongoDB架构时,使用shardKey选项。 每个分片集合都有一个分片键,它必须出现在所有插入/更新操作中。 我们只需要将此模式选项设置为相同的shard键,我们就会设置。
请注意,Mongoose不会为您发送shardcollection命令。 您必须自己配置您的碎片。

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

option:strict

strict选项(默认情况下已启用)确保传递给模型构造函数的,未在模式中指定的值不会保存到db。

 let Schema = mongoose.Schema;
 // 只定义了nane
let schema = new Schema({
    name:String 
});
let Person = mongoose.model('Person',schema);
// 多传了age
let man = new Person({
    name:'noshower',
    age:22
});
man.save(function(err,target){
    if(err){
        console.error(err+'xx');
    }else{
        console.log(target);//age没有被存进去
//{ __v: 0, name: 'noshower', _id: 58772602945ef339db63dc3d }
    }
});
//设置strict为false
let schema = new Schema({
    name:String 
},{
    strict:false
});
//此时age也存进去了。
//{ __v: 0,name: 'noshower',age: 22,_id: 5877285c8913073ae8e0146f }

option:toObject

与toObject选项完全相同,但仅在调用文档toJSON方法时才适用。

let Schema = mongoose.Schema;
let schema = new Schema({
    name:String
});
schema.path('name').get(function(v){
    return v+' is my name'
});
schema.set('toJSON',{getters:true,virtuals:false});
let Person = mongoose.model('Person',schema);
let man = new Person({
    name:'noshwoer'
});
console.log(man);//{ name: 'noshwoer', _id: 5877349b8c8cf73cafa89ada }
console.log(man.toObject());//{ name: 'noshwoer', _id: 5877349b8c8cf73cafa89ada }
console.log(man.toJSON());//{ name: 'noshwoer is my name', _id: 5877349b8c8cf73cafa89ada }
//当一个js对象被字符串化时,就会调用JSON。
console.log(JSON.stringify(man));//{"name":"noshwoer is my name","_id":"5877349b8c8cf73cafa89ada"}

option: toObject

文档有一个toObject方法,将mongoose文档转换为一个纯javascript对象。 此方法接受几个选项。 默认情况下,不是基于每个文档应用这些选项,我们可以在此声明选项,并将其应用于所有这些模式文档。要使所有虚拟显示在您的console.log输出中,请将toObject选项设置为{getters:true}:

let Schema = mongoose.Schema;
let schema = new Schema({
    name:String
});
schema.path('name').get(function(v){
    return v+' is my name'
});
schema.set('toObject',{getters:true});
let Person = mongoose.model('Person',schema);
let man = new Person({
    name:'noshwoer'
});
console.log(man);//{ name: 'noshwoer is my name', _id: 587736eafce76a3cddadd692, id: '587736eafce76a3cddadd692' }

option:versionKey

当文档第一次被Mongoose创建时,都会被设置versionKey属性。 此键值包含文档的内部版本。 此文档属性的名称是可配置的。 默认值为__v。 如果这与您的应用程序冲突,您可以这样配置:

let Schema = mongoose.Schema;
let schema = new Schema({
    name:String
});
let Person = mongoose.model('Person',schema);
let man = new Person({
    name:'noshwoer'
});
man.save(function(err,target){
    if(err){
        console.error(err);
    }else{
        console.log(target); //{ __v: 0, name: 'noshwoer', _id: 587739f5a090c53d34444e45 }
    }
});
// 下面自定义 versionkey
let schema = new Schema({
    name:String
},{
    versionKey:"_somethingElse"
});
 // _v 变成了_somethingElse
 // { _somethingElse: 0, name: 'noshwoer', _id: 58773a67f2ec543d46cdac5b }

还可以通过将versionKey设置为false来禁用文档版本控制。 除非你知道你在做什么,否则不要禁用版本控制。

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

现在我们已经介绍了Schemas,让我们来看看SchemaTypes。

SchemaTypes

以下是所有有效的SchemaTypes

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

Dates

如果必须使用内置方法修改日期类型,请在保存之前使用doc.markModified('pathToYourDate')告诉Mongoose关于更改。

 let Schema = mongoose.Schema;
let schema = new Schema({
    dueDate: Date
});
let Assignment = mongoose.model('Assignment', schema);
let time = new Assignment({
    dueDate:new Date()
});
Assignment.findOne(function(err,doc){
    doc.dueDate.setMonth(3);
    doc.save(function(err,doc){}); //没有保存
});
Assignment.findOne(function (err, doc) {
    doc.dueDate.setMonth(2);
    doc.markModified('dueDate');
    doc.save(function (err, doc) {
  //保存了
    });
});

Mixed

“任何事情”SchemaType,它的灵活性是在它的难以维护的权衡。 混合可以通过Schema.Types.Mixed或通过传递一个空对象字面量。 以下是等效的:

let Any = new Schema({ any: {} });
let Any = new Schema({ any: Schema.Types.Mixed });

由于它是一个无模式的类型,您可以将值更改为任何您喜欢的,但Mongoose失去自动检测和保存这些更改的能力。 要“告诉”Mongoose混合类型的值已更改,请调用文档的.markModified(路径)方法,将路径传递到刚更改的混合类型。

person.anything = { x: [3, 4, { y: "changed" }]};
person.markModified('anything');
person.save(); // anything will now get saved

ObjectIds

要指定ObjectId的类型,请在声明中使用Schema.Types.ObjectId。

let mongoose = require('mongoose');
let ObjectId = mongoose.Schema.Types.ObjectId;
let Car = new Schema({ driver: ObjectId });

Arrays

提供模式类型或子文档数组的创建。

  let ToySchema = new Schema({ name: String });
  let ToyBox = new Schema({
    toys: [ToySchema], 
    buffers: [Buffer], 
    string: [String], 
    numbers: [Number] // ... etc});
});

注意:指定空数组等效于Mixed。 以下都创建了Mixed的数组:

let Empty1 = new Schema({ any: [] });
let Empty2 = new Schema({ any: Array });
let Empty3 = new Schema({ any: [Schema.Types.Mixed] });
let Empty4 = new Schema({ any: [{}] });
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容