Schemas

2019-04-30  本文已影响0人  itvwork

定义一个 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的表
  });

静态方法

向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:

还没有写完,待更新……

上一篇下一篇

猜你喜欢

热点阅读