Schemas

定义一个 Schema

Mongoose工作都有开始于Schema。每个Schema对应(映射)一个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
    }
  });

如果你创建了Schema后,又想添加字段,可以使用Schema的add方法
代码中定义的每个字段属性都会转换成SchemaType.例如我们定了title字段,它将被转换成String 的SchemaType 并且字段date将换成Date SchemaType
字段的设置可以嵌套,像JOSN格式一样,比如meta字段

允许定的SchemaTypes 有:

查看更多SchemaTypes 点击这里.
Schemas不仅可以定义表的结构和属性,还能定义了文档实例方法静态模型方法复合索引中间件.。

创建Model

创建一个Model,我们必须要用到Schema,我们使用上面的blogSchema 创建一个Model,使用mongoose.model(modelName,blogSchema)

var Blog = mongoose.model('Blog', blogSchema);
  // 继续……

实例方法

Model是一个文档结构,有许多内置的方法。我们也可以自定义实例方法

 // 定义一个schema
  var animalSchema = new Schema({ name: String, type: String });

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

现在所有animalSchema的实例都有一个findSimilarTypes的方法

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

  dog.findSimilarTypes(function(err, dogs) {
    console.log(dogs); //查找 type是dog的表
  });
  • 如果重写Mongoose的文档内置方法可能会造成不可预料的错,点击这里查看更多详情

  • 上面的实例使用了Schema.methods对创建保存一个实例方法,点击这里查看Schema.method()的详情细介绍

  • 不要使用es6的箭头方法,关于es6简头方法的使用请自行查找

静态方法

向model添加一个静态方法也非常简单,我们继使用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的查询。查询助手方法允许可以链式使用,例如:

 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 支持二级索引. 我们可以使用mongoose在Schema 定义这些索引,可以使用以下两种方法:

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

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

当你的应用开始运行时,Mongoose会为Schema中定义的每一个索引自动创建索引,这个创建是依次的进行的,不是同时的时的。当所有索引创建完成时或出错误会在model上发送一个Index事件。虽然对开发者来说创建索引有利于开发,但是生产建不要使用索引,因为会对性能造成影响。可以禁用些功能。使用如下代码

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

开发时使用索引,可以使用以下方式监听Index事件


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

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

详细请查看 Model#ensureIndexes 方法。

虚拟(Virtuals)

Virtuals能获取和设置的文档属性,但不能持久化到MongoDB。getter用于格式化或组合字段,而setter用于将单个值分解为多个值进行存储。

  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链接起来非常麻烦,如果你想对name做一些特殊的处理,你可以使用Virtuals的get来处理。虚拟属性的getter允许您定义一个不会持久化到MongoDB的fullName属性。

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

//使用时,跟es6的get set有点相似
console.log(axl.fullName); 

如果你使用toJSON()toObject()时,mongoose默认没有包含虚拟属性的;这包括在Mongoose文档上调用JSON.stringify()的输出,因为JSON.stringify()调用toJSON()。将{virtuals: true}传递给toObject()toJSON()

你还可以为fullName设置一个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"

在其他验证之前应用虚拟属性设置器。因此,即使需要姓和名字段,上面的示例仍然有效。虚拟属性不能作为字段查询条件,因为数据库不存在该字段的

别名(Aliases)

别名是一种特殊的虚拟类型,getter和setter在其中无缝地获取和设置另一个属性。这对于节省网络带宽非常方便,因此可以将存储在数据库中的短属性名称转换为更长的名称,以提高代码的可读性。

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' }

还可以在嵌套路径上声明别名。使用嵌套模式和子文档更容易,但是也可以内联声明嵌套路径别名,只要使用完整的嵌套路径嵌套即可

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

选项

Schemas中有些一些配置选项,可直传给构造方法,也可以用set设置,如下:

new Schema({..}, options);

// 或

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

可设置的选项目有:

option: autoIndex

这个是否创建索引,默认情况下会开启这一选项,在开发和测试的时候还是很有用的。但在生产会产生比较大的负载,生产上建议关闭此选项,关闭方式如下:

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

这个选项默认是开启的,你可以关闭,使用mongoose.use('autoIndex', false);

option: autoCreate

在Mongoose构建索引之前,如果autoCreate设置为true,它调用Model.createCollection()在MongoDB中创建底层集合。调用createCollection()根据collation选项设置集合的默认排序规则,如果设置了capped模式选项,则将表建立为一个有上限的表。与autoIndex一样,将autoCreate设置为true有助于开发和测试环境。
不幸的是,createCollection()不能更改现有集合。例如,如果您将capped: 1024添加到您的模式中,而现有的集合没有被覆盖,createCollection()将抛出一个错误。通常,对于生产环境,autoCreate应该为false。

option: bufferCommands

这个选项主是当mongoose 中断了链接会自动重新链接,直到驱动程序重新连接为止,禁用请设置为false

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

option: capped

设置Mongodb表大小,单字为字节

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

如查你要对该属性,设置maxautoIndexId属性,那么你可以传一个对象,但该对象一定要包含size属性

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

option: collection

Mongodb表名,默认会生一个表名,但是最后此项不要用默认,最好给表设个名字

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

option: id

是否给Schema添加一个虚拟id

// 默认情况下
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
});

可存数据的时候没有_id是没无保存数据的,最好不要禁用此功能

option: minimize

是否删除空对象,以此来减小表所占的大小

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

// 如果“inventory”字段不是空的,将会存储它
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”字段是空的,将不会存储它
const sam = new Character({ name: 'Sam', inventory: {}});
await sam.save();
doc = await Character.findOne({ name: 'Sam' }).lean();
doc.inventory; // undefined

可以通过以下代码设置minimize属

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

// 如果nventory是空对象时,会存个空对象进去
const sam = new Character({ name: 'Sam', inventory: {} });
await sam.save();
doc = await Character.findOne({ name: 'Sam' }).lean();
doc.inventory; // {}

判断对象是否是空对象可以使用$isEmpty()

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

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

option: read(暂时还不是很明白此项目的作用,后期弄懂再补)

这个选项是给Schema起一个别名,配合### 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

设置mongodb写入策略(WriteConcern),具体说明请自行查找mongodb

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

option: safe

safe是一个与writeConcern类似的遗留选项。特别是,safe: false是未确认的写入writeConcern: {w: 0}的缩写。而是使用writeConcern选项。

option: shardKey

设置分片的时候用到;分片必须自行配置,详情请自行查看mongodb文档

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

option: strict(严格模式)

这个选是默认开启的,开启这个选项,如果你的Schema没有这个指定的字段,那么mongoose将不保存到数据库中(建议开启这个选项)

var thingSchema = new Schema({..})
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // 如查开启将不会保存到数据库

// set to false..
var thingSchema = new Schema({..}, { strict: false });
var thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // 关闭会保存数据到数据库

也可以通过以下方式开启/关闭strict

var Thing = mongoose.model('Thing');
var thing = new Thing(doc, true);  // 第二个参数就strict的值,strict设为true
var thing = new Thing(doc, false); // 第二个参数就strict的值,strict设为false

可以将“strict”设置为“throw”,抛出错误会,而不是过滤没有的数据
*注意,如查实例不存在的话,你怎么样设置这个参数都是不起作用的

var thingSchema = new Schema({..})
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing;
thing.iAmNotInTheSchema = true;
thing.save(); // iAmNotInTheSchema  数据不会被保存

字段不向后兼容,strict选项不适用查询时过滤在Schema不存在的数据

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

// mongoose不会过滤notInSchema,因为strict对于查询是不起作用的
MyModel.find({ notInSchema: 1 });

strict选项支持更新数据

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

option: strictQuery

该字段与上文的strict相对应,这个字段是在查询的时候过滤Schema没有的字段查询数据

const mySchema = new Schema({ field: Number }, {
  strict: true,
  strictQuery: true // 打开查询过滤模式(严格模式)
});
const MyModel = mongoose.model('Test', mySchema);

// Mongoose会过滤掉'{ notInSchema: 1 }'因已经打开查询过滤模式,此条件无效
MyModel.find({ notInSchema: 1 });

option: toJSON

与toObject选项完全相同,但仅在调用documents 对象的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' }
// 只要js对象被转为字符串,就会调用toJSON
console.log(JSON.stringify(m)); // { "_id": "504e0cd7dd992d9be2f20b6f", "name": "Max Headroom is my name" }

option: toObject

documents 有一个toObject方法,它将mongoose文档转换为一个普通的javascript对象。此方法接受几个选项。我们可以在这里声明这些选项,并在缺省情况下将其应用于所有这些模式文档,而不是根据每个文档应用这些选项
要让所有virtuals对象都显示在console.log输出中,请将toObject选项设置为{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

在默认的情况下,如果你在Schema设置了一个字段,mongoose将认为你这是类型声明

// Mongoose 将认为loc是一个字符串
var schema = new Schema({ loc: { type: String, coordinates: [Number] } });

如果你需要type是一个字段,而不是作为字段声明的类型,那么你可以设置这个参数

var schema = new Schema({
  // Mongoose将认为loc是一个对象,而不是一个字符串
  loc: { type: String, coordinates: [Number] },
  // Mongoose将认为name是一个字符串,而不是一个对象
  name: { $type: String }
}, { typeKey: '$type' }); // 用$type替换type

option: validateBeforeSave

默认情况下,文档在保存到数据库之前会自动验证。这是为了防止保存无效的数据。如果希望手动处理验证,并能够保存没有通过验证的数据,可以将validatebeforeave设置为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,在你最开始创建文档插入数据时已经创建了,一个记更改,添加的版本号,默认的字段是__v,如果字段跟你的业务逻辑有冲突,你可以像下面一样写

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' }

版本号控制不是一个很可靠的解决并发的方案.
Note that Mongoose versioning is not a full optimistic concurrency solution. Use mongoose-update-if-currentfor OCC support. Mongoose versioning only operates on arrays:

还没有写完,待更新……

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

推荐阅读更多精彩内容