定义一个 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.first
与name.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);
可设置的选项目有:
- [autoIndex](#option: autoIndex)
- [bufferCommands](#option: bufferCommands)
- capped
- collection
- id
- _id
- minimize
- read
- writeConcern
- safe
- shardKey
- strict
- strictQuery
- toJSON
- toObject
- typeKey
- validateBeforeSave
- versionKey
- collation
- skipVersioning
- timestamps
- selectPopulatedPaths
- storeSubdocValidationError
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 });
如查你要对该属性,设置max
与autoIndexId
属性,那么你可以传一个对象,但该对象一定要包含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:
还没有写完,待更新……