首先需要安装MongoDB和Node.js。 然后使用npm下载mongoose: npm install mongoose
接着我们直接在项目中引入mongoose
,并且连接数据库就会在本地运行 MongoDB
了:
用node命令执行index.js,demo数据库就会在本地运行了。我们需要在连接数据库成功或者失败的时候有些提示,可以这样添加代码:
var db = mongoose.connection;db.on('error', console.error.bind(console, 'connection error:'));db.once('open', function (callback) { // 后续代码默认都写在这里!});连接成功的时候就会执行一次回调函数,现在假设下面所有代码都是在成功的回调函数中的。
在Mongoose
中,所有东西都是衍生自Schema
。 Schema(模式)
就像是Model(模型)
的抽象类一样的存在,创建一个Schema
:
我们创建了一个叫mySchema
的Schema
,并且定义了一个属性name
,限定类型是String
。接着可以用mySchema
创建一个Model
:
这样就按照mySchema
创建了一个MyModel
模型,这个MyModel
其实充当了两个作用:
所以要构造一条document
只要:
instance
就是一条也可以称作Document
了。
如此看来Schema
、Model
和Document
的关系就像是抽象类、实现类和实例三者的关系一样。所以嘛,自然可以在Schema
定义后添加方法:
instance
实例现在还没有被保存在MongoDB中,所有的实例都要调用save
方法后才会保存进数库:
而后,需要查询数据时就需要使用Model
,因为Model的作用时充当MongoDB中的collection(集合)
,所以这样查询数据:
当然了,也可以加条件查询:
MyModel.find({name:"instance"},callback);这样就是一个完整的使用mongoose插入和查询数据的操作了。完整代码如下:
var mongoose = require('mongoose');var db = mongoose.connection;db.on('error', console.error.bind(console, 'connection error:'));db.once('open', function(callback) { var mySchema =new mongoose.Schema({ name: String }); mySchema.methods.getName = function() { console.log(this.name + "来自getName方法"); } var MyModel = mongoose.model('MyModel', mySchema); var instance = new MyModel({ name: ' Instance' }) console.log(instance.name) // 'instance' instance.getName(); //'instance' instance.save((err, instance) => { if (err) { return console.error(err) }; instance.getName(); })});mongoose.connect('mongodb://localhost/test');Mongoose所有的一切始于Schema。每一个Schema都映射到MongoDB中的collection(集合)和定义了document(文档)的结构。
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
以后添加额外的key
,可以使用Schema的add
方法。
现在只需要知道SchemaTypes有:
StringNumberDateBufferBooleanMixedObjectIdArray创建了Schema
后肯定就是创建Model
了,然而,在这之间我们还可以定义instance方法、定义静态Model方法、定义索引和使用生命周期挂钩(中间件)
instance方法使用schema
的methods
添加,这样做可以让创建的documents在save之前执行一些自定义的操作:
这样做要注意的是不要重写mongoose原来的document方法,不然会有未知错误。
在methods
上添加的是给document用的,而在statics
上添加的方法就是给Model用的:
MongoDB支持使用索引。在mongoose中,分别可以在内部和外部定义,不过复合索引就只能在外部定义了:
var animalSchema = new Schema({ name: String, type: String, tags: { type: [String], index: true } // 内部});animalSchema.index({ name: 1, type: -1 }); // 外部,复合索引当应用启动,Mongoose就会自动调用ensureIndex
为每个schema创建索引。索引创建会有显著的性能影响,所以建议在生产环境中禁用:
虚拟属性是一种不会存储进数据库但是存在在doucment中的属性。充当getter
和setter
的功能。 基本代码:
当调用toObject和toJSON方法时默认都是不会有虚拟属性的。 现在想访问bad.name.full
就给出全名,就要使用虚拟属性的getter
功能:
同样的有setter
功能:
Schemas在构建实例或者通过set
方法可以进行有options的配置:
options:
autoIndex:自动索引cappedcollectionid_idreadsafeshardKeystricttoJSONtoObjectversionKey
在应用启动时,Mongoose会调用ensureIndex为Schema构建索引。自Mongoose v3起,索引默认都会在后台创建。如果想关闭自动创建或者以后手动创建索引,可以进行如下设置:
var schema = new Schema({..}, { autoIndex: false });var Clock = mongoose.model('Clock', schema);Clock.ensureIndexes(callback);Mongoose支持MongoDB的固定大小集合,直接设置capped
表示最大空间,单位时bytes
,当然也可以使用对象设置max
(最大document数)和autoIndexId
:
Mongoose中collection的名字默认时注册Model时的名字,如果想要自定义,可以这样设置:
var dataSchema = new Schema({..}, { collection: 'data' });document
都会设置一个虚拟属性id
并配置getter
来获取_id
,如果不想要id
虚拟属性可以设为false
:
Mongoose默认会在生成document的时候生成_id
字段,如果想禁止这个行为可以设为false
,但是插入数据库的时候仍然会有_id
字段:
设置读写分离属性:
var schema = new Schema({..}, { read: 'PRimary' }); // also aliased as 'p'var schema = new Schema({..}, { read: 'primaryPreferred' }); // aliased as 'pp'var schema = new Schema({..}, { read: 'secondary' }); // aliased as 's'var schema = new Schema({..}, { read: 'secondaryPreferred' }); // aliased as 'sp'var schema = new Schema({..}, { read: 'nearest' }); // aliased as 'n'mongoose默认会开启严格模式,所有不是在Schema定义的属性都不会被保存进数据库,将strict设为false就会:
var thingSchema = new Schema({..})var Thing = mongoose.model('Thing', thingSchema);var thing = new Thing({ iAmNotInTheSchema: true });thing.save(); // iAmNotInTheSchema不会保存进数据库// 设为 false..var thingSchema = new Schema({..}, { strict: false });var thing = new Thing({ iAmNotInTheSchema: true });thing.save(); // iAmNotInTheSchema会保存进数据库还支持在instance的时候设置:
var thing = new Thing(doc, false); // 关闭严格模式除了boolean,也可以设置为throw
,但是这样会抛出错误,而不时忽略值。 提示:不要手贱设为false 提示:在mongoose v2 默认只时false 提示:直接在document上set的只都不会被保存
两个方法类似,都是输出格式化对象:
var schema = new Schema({ name: String });schema.path('name').get(function (v) { return v + ' is my name';});//默认是不使用getter和不输出virtualschema.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' }// stringify内部会调用toJSONconsole.log(JSON.stringify(m));//console内部时调用toObjectconsole.log(m);设置document的version键,默认键是_v
,设为false的话就没有这个version:
Models在Schema和document中作承上启下,作用有两个: - 充当MongoDB中的collection(集合) - 是用来构造document(文档)的类 所以document的创建和检索都是来自于Models的方法。
查询操作有诸如find
, findById
, findOne
, 和 where
等方法,直接查API:
Models有个remove方法可以用于移除指定条件的documents:
Tank.remove({ size: 'large' }, function (err) { if (err) return handleError(err); // removed!});Models有个update方法可以用于更新指定条件的documents:
let query = { age: { $gt: 18 } };//查询条件let updated = { oldEnough: true };//更新结果 也可以是{ $set: { name: 'jason borne' }}MyModel.update(query,updated ,options, fn);如果说Models(也就为collection)相当于SQL数据库中的表的话,那么Document就相当于行了。
数据的更新操作也可以直接使用在document:
Tank.findById(id, function (err, tank) { if (err) return handleError(err); tank.size = 'large'; tank.save(function (err) { if (err) return handleError(err); res.send(tank); });});在构建Schema可以使用另外一个Schema作为数据类型:
var childSchema = new Schema({ name: 'string' });var parentSchema = new Schema({ children: [childSchema]})保存文档的时候,子文档在数据库并不会独自使用集合保存,数据库中保存的只有父文档的集合。不过每个子文档都会有一个_id
:
但是子文档如果的生命周期有挂钩的话也是会执行的:
childSchema.pre('save', function (next) { if ('invalid' == this.name) return next(new Error('#sadpanda')); next();});var parent = new Parent({ children: [{ name: 'invalid' }] });parent.save(function (err) { console.log(err.message) // #error : sadpanda})MongooseArray方法有想 push, unshift, addToSet等等可以添加子文档:
var Parent = mongoose.model('Parent');var parent = new Parent;// create a commentparent.children.push({ name: 'Liesl' });var subdoc = parent.children[0];console.log(subdoc) // { _id: '501d86090d371bab2c0341c5', name: 'Liesl' }subdoc.isNew; // trueparent.save(function (err) { if (err) return handleError(err) console.log('Success!');});//或者var newdoc = parent.children.create({ name: 'Aaron' });移除子文档可以使用remove方法:
var doc = parent.children.id(id).remove();parent.save(function (err) { if (err) return handleError(err); console.log('the sub-doc was removed')});在v3版本允许直接声明对象声明子文档:
var parentSchema = new Schema({ children: [{ name: 'string' }]})所有model的查询操作都会有两种形式: - 当有回调方法作为参数时:会将查询操作的结果传递给回调方法; - 当没有回调方法作为参数时:会将查询结果封装成一个QueryBuilder接口返回。 先看看有回调方法是怎样的:
var Person = mongoose.model('Person', yourSchema);// find each person with a last name matching 'Ghost', selecting the `name` and `occupation` fieldsPerson.findOne({ 'name.last': 'Ghost' }, 'name occupation', function (err, person) { if (err) return handleError(err); console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation) // Space Ghost is a talk show host.})在Mongoose中的查询回调方法的形式都类似于:callback(error, result)
。result不一定都是document的list,要看具体是什么操作。
再来看看没有回调函数的写法:
// find each person with a last name matching 'Ghost'var query = Person.findOne({ 'name.last': 'Ghost' });// selecting the `name` and `occupation` fieldsquery.select('name occupation');// execute the query at a later timequery.exec(function (err, person) { if (err) return handleError(err); console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation) // Space Ghost is a talk show host.})//也可以使用链式操作Person.findOne({ 'name.last': 'Ghost' }).select('name occupation').exec(callback);上面三种写法是做同一件事情,不加回调参数时,要使用exec
才会执行所有操作。
Mongoose有几个内置的验证器: - 所有的 SchemaTypes 都可以声明required
; - Nembers 有 min
和max
; - String有enum
和match
验证。 所有的这些都可以在 Schema创建时进行声明。
用户还可以使用validate
方法自定义验证规则:
验证错误触发后,document会有一个errors属性:
toy.errors.color.message === err.errors.color.messageMongoose允许在文档的init
,validate
,save
和remove
的前后触发一些方法
前置有两种形式,串行和并行。
pre
可以将传递错误:
Post中间件是在指定操作完成后,回调函数还没有执行前执行的方法:
parentSchema.pre("save", function(next, done) { console.log("pre save"); next(); console.log("after pre save"); })parentSchema.post("save", function() { console.log("post save"); })...parent.save(function(err) { if (err) { console.log(err); return; } console.log("save"); });/*consolepre saveafter pre savepost savesave*/MongoDB是文档型数据库,所以没有关系型数据库joins(数据库的两张表通过”外键”,建立连接关系。) 特性。建立数据关联时会非常麻烦,Mongoose就封装了Population实现document中填充其他collection的document。
能建立关联的字段只有ObjectId, Number, String, and Buffer四种类型可以。建立关联只需要在声明Schema的时候使用ref
属性就可以关联:
storySchema中_creator和fans字段都关联了Person,并且都将type设为Number。这是因为,Person和Story建立了关联后,Story中的document的_creator或fans字段是通过Person的_id属性关联对应数据的,所以Story的_creator和fans要与Person的_id类型保持一致。
要先保存被关联的document(Person),并且将_id注册进去:
var aaron = new Person({ _id: 0, name: 'Aaron', age: 100 });aaron.save(function (err) { if (err) return handleError(err); var story1 = new Story({ title: "Once upon a timex.", _creator: aaron._id // 这里可以直接用aaron }); story1.save(function (err) { if (err) return handleError(err); // thats it! });})当然了注册的时候直接写个0
也可以,这个ref只是在检索Person的_id字段的依据的时候。
现在只需要在关联查询的时候使用populate
声明关联字段就会进行关联查询的:
_creator
字段就会被关联document给替换了。数组也是同样的道理,每个元素都会被相应的document给替换。
可以对被关联的document进行筛选:
Story.findOne({ title: /timex/i }).populate('_creator', 'name') // 只返回name字段.exec(function (err, story) { if (err) return handleError(err); console.log('The creator is %s', story._creator.name); // prints "The creator is Aaron" console.log('The creators age is %s', story._creator.age); // prints "The creators age is null'})在3.6版本后可以使用空格分割populate:
Story.find(...).populate('fans author') //使用空格分开.exec()但是在3.6之前就只能链式操作:
Story.find(...).populate('fans').populate('author').exec()查询的时候可以使用其他参数:
Story.find(...).populate({ path: 'fans', match: { age: { $gte: 21 }}, select: 'name -_id', options: { limit: 5 }}).exec()Schemas允许添加插件,这样就会想继承一样,每个Schemas都会有插件中定义的功能:
// lastMod.jsmodule.exports = exports = function lastModifiedPlugin (schema, options) { schema.add({ lastMod: Date }) schema.pre('save', function (next) { this.lastMod = new Date next() }) if (options && options.index) { schema.path('lastMod').index(options.index) }}// game-schema.jsvar lastMod = require('./lastMod');var Game = new Schema({ ... });Game.plugin(lastMod, { index: true });// player-schema.jsvar lastMod = require('./lastMod');var Player = new Schema({ ... });Player.plugin(lastMod);Game和Player就都会有lastMod中定义的功能。
新闻热点
疑难解答