mongoose 笔记

拓拔高畅
2023-12-01

快速启动

首先需要安装MongoDB和Node.js
然后使用npm下载mongoose:
npm install mongoose
接着我们直接在项目中引入mongoose,并且连接数据库就会在本地运行 MongoDB了:

// index.js
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/demo');

用node命令执行index.js,demo数据库就会在本地运行了。我们需要在连接数据库成功或者失败的时候有些提示,可以这样添加代码:

var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function (callback) {
  // 后续代码默认都写在这里!
});

连接成功的时候就会执行一次回调函数,现在假设下面所有代码都是在成功的回调函数中的。

Mongoose中,所有东西都是衍生自SchemaSchema(模式)就像是Model(模型)的抽象类一样的存在,创建一个Schema

var mySchema =new mongoose.Schema({
    name: String
});

我们创建了一个叫mySchemaSchema,并且定义了一个属性name,限定类型是String。接着可以用mySchema创建一个Model

var MyModel = mongoose.model('MyModel', mySchema);

这样就按照mySchema创建了一个MyModel模型,这个MyModel其实充当了两个作用:

  • 充当MongoDB中的collection(集合)
  • 是用来构造document(文档)的类

所以要构造一条document只要:

var  instance = new MyModel({ name: ' Instance' })
console.log( instance.name) // 'instance'

instance就是一条也可以称作Document了。

如此看来SchemaModelDocument的关系就像是抽象类、实现类和实例三者的关系一样。所以嘛,自然可以在Schema定义后添加方法:

var mySchema =new mongoose.Schema({
    name: String
});
mySchema.methods.getName=function(){
    console.log(this.name + "来自getName方法");
}

...

instance.getName();//'instance来自getName方法'

instance实例现在还没有被保存在MongoDB中,所有的实例都要调用save方法后才会保存进数库:

instance.save((err,instance)=>{
    if (err) return console.error(err);
    instance.getName();
})

而后,需要查询数据时就需要使用Model,因为Model的作用时充当MongoDB中的collection(集合),所以这样查询数据:

MyModel.find(function (err, instances) {
  if (err) return console.error(err);
  console.log(instances);
});

当然了,也可以加条件查询:

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

Schemas

定义schema

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

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

创建了Schema后肯定就是创建Model了,然而,在这之间我们还可以定义instance方法、定义静态Model方法、定义索引和使用生命周期挂钩(中间件)

instance方法

instance方法使用schemamethods添加,这样做可以让创建的documents在save之前执行一些自定义的操作:

// define a schema
var animalSchema = new Schema({ name: String, type: String });

// assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function (cb) {
  return this.model('Animal').find({ type: this.type }, cb);
}

//define a model
var Animal = mongoose.model('Animal', animalSchema);

//instance
var dog = new Animal({ type: 'dog' });
dog.findSimilarTypes(function (err, dogs) {
  console.log(dogs); // woof
});

这样做要注意的是不要重写mongoose原来的document方法,不然会有未知错误。

静态Model方法

methods上添加的是给document用的,而在statics上添加的方法就是给Model用的:

// assign a function to the "statics" object of our animalSchema
animalSchema.statics.findByName = function (name, cb) {
  this.find({ name: new RegExp(name, 'i') }, cb);
}
var Animal = mongoose.model('Animal', animalSchema);
Animal.findByName('fido', function (err, animals) {
  console.log(animals);
});

索引

MongoDB支持使用索引。在mongoose中,分别可以在内部和外部定义,不过复合索引就只能在外部定义了:

var animalSchema = new Schema({
  name: String,
  type: String,
  tags: { type: [String], index: true } // 内部
});
animalSchema.index({ name: 1, type: -1 }); // 外部,复合索引

当应用启动,Mongoose就会自动调用ensureIndex为每个schema创建索引。索引创建会有显著的性能影响,所以建议在生产环境中禁用:

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

虚拟属性

虚拟属性是一种不会存储进数据库但是存在在doucment中的属性。充当gettersetter的功能。
基本代码:

var personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
});
// compile our model
var Person = mongoose.model('Person', personSchema);
// create a document
var bad = new Person({
    name: { first: 'Walter', last: 'White' }
});

当调用toObject和toJSON方法时默认都是不会有虚拟属性的。
现在想访问bad.name.full就给出全名,就要使用虚拟属性的getter功能:

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

。。。

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

同样的有setter功能:

personSchema.virtual('name.full').set(function (name) {
  var split = name.split(' ');
  this.name.first = split[0];
  this.name.last = split[1];
});
...
bad.name.full = 'Breaking Bad';
console.log(bad.name.first); // Breaking
console.log(bad.name.last);  // Bad

options

Schemas在构建实例或者通过set方法可以进行有options的配置:

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

options:

  • autoIndex:自动索引
  • capped
  • collection
  • id
  • _id
  • read
  • safe
  • shardKey
  • strict
  • toJSON
  • toObject
  • versionKey

    autoIndex

    在应用启动时,Mongoose会调用ensureIndex为Schema构建索引。自Mongoose v3起,索引默认都会在后台创建。如果想关闭自动创建或者以后手动创建索引,可以进行如下设置:

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

bufferCommands

capped

Mongoose支持MongoDB的固定大小集合,直接设置capped表示最大空间,单位时bytes,当然也可以使用对象设置max(最大document数)和autoIndexId

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

collection

Mongoose中collection的名字默认时注册Model时的名字,如果想要自定义,可以这样设置:

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

id

document都会设置一个虚拟属性id并配置getter来获取_id,如果不想要id虚拟属性可以设为false

// default behavior
var schema = new Schema({ name: String });
var Page = mongoose.model('Page', schema);
var p = new Page({ name: 'mongodb.org' });
console.log(p.id); // '50341373e894ad16347efe01'
// disabled id
var schema = new Schema({ name: String }, { id: false });
var Page = mongoose.model('Page', schema);
var p = new Page({ name: 'mongodb.org' });
console.log(p.id); // undefined

_id

Mongoose默认会在生成document的时候生成_id字段,如果想禁止这个行为可以设为false,但是插入数据库的时候仍然会有_id字段:

// default behavior
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' }
// disabled _id

var schema = new Schema({ name: String }, { _id: false });
// 不要使用schema.set("_id",false),
var Page = mongoose.model('Page', schema);
var p = new Page({ name: 'mongodb.org' });
console.log(p); // { name: 'mongodb.org' }
// MongoDB 会在数据被插入的时候给_id
p.save(function (err) {
  if (err) return handleError(err);
  Page.findById(p, function (err, doc) {
    if (err) return handleError(err);
    console.log(doc); // { name: 'mongodb.org', _id: '50341373e894ad16347efe12' }
  })
})

read

设置读写分离属性:

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'

safe

shardKey

strict

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 thingSchema = new Schema({..})
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing({},false);
thing.iAmNotInTheSchema = true;
thing.save(); // iAmNotInTheSchema is never saved to the db

toJSON和toObject

两个方法类似,都是输出格式化对象:

var schema = new Schema({ name: String });
schema.path('name').get(function (v) {
  return v + ' is my name';
});

//默认是不使用getter和不输出virtual
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' }

// stringify内部会调用toJSON
console.log(JSON.stringify(m));

//console内部时调用toObject
console.log(m);

versionKey

设置document的version键,默认键是_v,设为false的话就没有这个version:

var schema = new Schema({ name: 'string' });
var Thing = mongoose.model('Thing', schema);
var thing = new Thing({ name: 'mongoose v3' });
thing.save(); // { __v: 0, name: 'mongoose v3' }
// 换 versionKey
new Schema({..}, { versionKey: '_somethingElse' })
var Thing = mongoose.model('Thing', schema);
var thing = new Thing({ name: 'mongoose v3' });
thing.save(); // { _somethingElse: 0, name: 'mongoose v3' }
//设为false
new Schema({..}, { versionKey: false });
var Thing = mongoose.model('Thing', schema);
var thing = new Thing({ name: 'no versioning please' });
thing.save(); // { name: 'no versioning please' }

Models

Models在Schema和document中作承上启下,作用有两个:
- 充当MongoDB中的collection(集合)
- 是用来构造document(文档)的类
所以document的创建和检索都是来自于Models的方法。

第一个model

var schema = new mongoose.Schema({ name: 'string', size: 'string' });
var Tank = mongoose.model('Tank', schema);

构造documents

var Tank = mongoose.model('Tank', yourSchema);
var small = new Tank({ size: 'small' });
small.save(function (err) {
  if (err) return handleError(err);
  // saved!
})
// or
Tank.create({ size: 'small' }, function (err, small) {
  if (err) return handleError(err);
  // saved!
})

查询

查询操作有诸如find, findById, findOne, 和 where等方法,直接查API:

Tank.find({ size: 'small' }).where('createdDate').gt(oneYearAgo).exec(callback);

移除

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

Documents

如果说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

var Parent = mongoose.model('Parent', parentSchema);
var parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] })
parent.children[0].name = 'Matthew';
parent.save(callback);//保存后数据库中只有Parent集合


var doc = parent.children.id(id);//使用`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 comment
parent.children.push({ name: 'Liesl' });
var subdoc = parent.children[0];
console.log(subdoc) // { _id: '501d86090d371bab2c0341c5', name: 'Liesl' }
subdoc.isNew; // true
parent.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` fields
Person.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` fields
query.select('name occupation');
// execute the query at a later time
query.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 有 minmax
- String有enummatch验证。
所有的这些都可以在 Schema创建时进行声明。

自定义验证

用户还可以使用validate方法自定义验证规则:

var toySchema = new Schema({
  color: String,
  name: String
});
var Toy = mongoose.model('Toy', toySchema);
//自定义验证规则
Toy.schema.path('color').validate(function (value) {
  return /blue|green|white|red|orange|periwinkle/i.test(value);
}, 'Invalid color');
var toy = new Toy({ color: 'grease'});
toy.save(function (err) {
  // err is our ValidationError object
  // err.errors.color is a ValidatorError object

  console.log(err.errors.color.message) // prints 'Validator "Invalid color" failed for path color with value `grease`'
  console.log(String(err.errors.color)) // prints 'Validator "Invalid color" failed for path color with value `grease`'
  console.log(err.errors.color.type)  // prints "Invalid color"
  console.log(err.errors.color.path)  // prints "color"
  console.log(err.errors.color.value) // prints "grease"
  console.log(err.name) // prints "ValidationError"
  console.log(err.message) // prints "Validation failed"
});

验证错误触发后,document会有一个errors属性:

toy.errors.color.message === err.errors.color.message

中间件

Mongoose允许在文档的initvalidatesaveremove的前后触发一些方法

Pre

前置有两种形式,串行和并行。

串行

var schema = new Schema(..);
schema.pre('save', function (next) {
  // 中间件一个接一个
  next();
});

并行

var schema = new Schema(..);
schema.pre('save', true, function (next, done) {
  // 异步执行中间件
  next();
  doAsync(done);
});

错误传递

pre可以将传递错误:

schema.pre('save', function (next) {
  var err = new Error('something went wrong');
  next(err);
});
// later...
myDoc.save(function (err) {
  console.log(err.message) // something went wrong
});

Post

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");
    });
/*console
pre save
after pre save
post save
save
*/

Population

MongoDB是文档型数据库,所以没有关系型数据库joins(数据库的两张表通过”外键”,建立连接关系。) 特性。建立数据关联时会非常麻烦,Mongoose就封装了Population实现document中填充其他collection的document。

建立关联

能建立关联的字段只有ObjectId, Number, String, and Buffer四种类型可以。建立关联只需要在声明Schema的时候使用ref属性就可以关联:

var mongoose = require('mongoose')
  , Schema = mongoose.Schema

var personSchema = Schema({
  _id     : Number,
  name    : String,
  age     : Number,
  stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
  _creator : { type: Number, ref: 'Person' },
  title    : String,
  fans     : [{ type: Number, ref: 'Person' }]
});
var Story  = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);

storySchema中_creator和fans字段都关联了Person,并且都将type设为Number。这是因为,Person和Story建立了关联后,Story中的document的_creator或fans字段是通过Person的_id属性关联对应数据的,所以Story的_creator和fans要与Person的_id类型保持一致。

保存refs

要先保存被关联的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声明关联字段就会进行关联查询的:

Story
.findOne({ title: 'Once upon a timex.' })
.populate('_creator')
.exec(function (err, story) {
  if (err) return handleError(err);
  console.log('The creator is %s', story._creator.name);
  // prints "The creator is Aaron"
})

_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()

Plugins

Schemas允许添加插件,这样就会想继承一样,每个Schemas都会有插件中定义的功能:

// lastMod.js
module.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.js
var lastMod = require('./lastMod');
var Game = new Schema({ ... });
Game.plugin(lastMod, { index: true });

// player-schema.js
var lastMod = require('./lastMod');
var Player = new Schema({ ... });
Player.plugin(lastMod);

Game和Player就都会有lastMod中定义的功能。

 类似资料: