一、mongoose


  • 安装:npm install mongoose
  1. // 1,引入mongoose
  2. const mongoose = require('mongoose')
  3. // 2. 连接本地数据库
  4. let db = mongoose.connect('mongodb://localhost/test')
  5. const db = mongoose.connection;
  6. db.on('error', console.error.bind(console, 'connection error:'));
  7. db.once('open', function() {
  8. // we're connected!
  9. });

mongoose里,一切始于Schema:

  1. let tomSchema = mongoose.Schema({
  2. name:String
  3. })
  4. //接着,把这个Schema编译成一个Model
  5. let Tom = mongoose.model('Tom',tomSchema)
  6. // 给这个model加一个code方法
  7. Tom.methods.code = function(){
  8. let nickname = this.name ? "The programmar name is :" + this.name:'I don't have name'
  9. console.log(nickname)
  10. }
  11. //model是我们构造document的class,每个document都是一个Tom对象
  12. let Tomliu = new Tom({name:'liugezhou'})
  13. Tomliu.code() //The programmar name is :liugezhou
  14. // save
  15. Tomliu.save(function(err,item){
  16. if (err) return console.error(err);
  17. item.speak();
  18. })
  19. // 获取所有的Tom
  20. Tom.find(function(err,tomlius){
  21. if(err) return console.error(err);
  22. console.log(tomlius)
  23. })
  24. //获取特定数据
  25. Tom.find({name:/^liugezhou/},callback)

二、Schema-模式


  • 每个Schema都会映射到MongoDB 的collection,并定义这个collection里的文档构成

语法:

  • const shcema = mongoose.Schema({})

允许使用的Schematypes有:

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

除了映射collection外,还可以定义

  • document的instance methods
  • model的static Model methods
  • 复合索引
  • 文档的生命周期钩子,也成为中间件

model

我们要把一个Schema转化为一个model,要使用

  • let model = mongoose.model(modelName,schema) 函数

collection和document

  • collection相当于关系型数据库中的表
  • document相当于一条数据,在这里有特别需要注意的一点是:
  • collection不要求文档有相同的结构,在一个collection文档中不必具有相同的fileds,对于单个field在一个collection中的不同文档中可以是不同的数据类型

实例方法methods

  • documents是model的实例,document有自带的实例方法,当然也可以自定义我们自己的方法。
  1. const animalSchema = mongoose.Schema({type:String,name:String})
  2. animalSchema.methods.findSameType = function (cb){
  3. return this.model('Animal').find(type:this.type,cb)
  4. }
  5. const Animal = mongoose.model('Animal',animalSchema)
  6. const dog = new Animal({type:'dog'})
  7. dog.findSameType(function(err,dogs){
  8. console.log(dogs)
  9. })

静态方法

  • 静态方法与实例方法的区别是,实例方法是在每个model的实例中可以访问,而静态方法是每个model直接访问
  1. animalSchema.statics.findByName = function(name,cb) {
  2. return this.find({name:new RegExp(name,'i')},cb)
  3. }
  4. const Animal = mongoose.model('Animal',animalSchema)
  5. Animal.findByName('fido',function(err,animal){
  6. console.log(animals)
  7. })

查询助手

  • 查询助手作用于query实例,方便定义自己的查询扩展
  1. animalSchema.query.byName = function(name) {
  2. return this.find({ name: new RegExp(name, 'i') });
  3. };
  4. var Animal = mongoose.model('Animal', animalSchema);
  5. Animal.find().byName('fido').exec(function(err, animals) {
  6. console.log(animals);
  7. });

索引

  • Mongodb支持secondary indexes,在mongoose中,我们在Schema中定义索引,索引字段级别和shcema级别
  1. var animalSchema = new Schema({
  2. name: String,
  3. type: String,
  4. tags: { type: [String], index: true } // field level
  5. });
  6. animalSchema.index({ name: 1, type: -1 }); // schema level

虚拟值 Virtual

  • Virtual是document的属性,但是不会保存到MongoDB,getter可以用于格式化和组合字段数据,setter可以很方便的分解一个值到多个字段。
  1. // define a schema
  2. var personSchema = new Schema({
  3. name: {
  4. first: String,
  5. last: String
  6. }
  7. });
  8. // compile our model
  9. var Person = mongoose.model('Person', personSchema);
  10. // create a document
  11. var axl = new Person({
  12. name: { first: 'Axl', last: 'Rose' }
  13. });
  • 如果你要log出全名,可以这么做:
    1. console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose
  • 但是每次都这么拼接实在太麻烦了, 推荐你使用virtual property getter, 这个方法允许你定义一个 fullName 属性,但不必保存到数据库。
  1. personSchema.virtual('fullName').get(function () {
  2. return this.name.first + ' ' + this.name.last;
  3. });
  • 现在, mongoose 可以调用 getter 函数访问 fullName 属性:
    1. console.log(axl.fullName); // Axl Rose
  • 如果对 document 使用 toJSON() 或 toObject(),默认不包括虚拟值, 你需要额外向 toObject() 或者 toJSON() 传入参数 { virtuals: true }

你也可以设定虚拟值的 setter ,下例中,当你赋值到虚拟值时,它可以自动拆分到其他属性:

  1. personSchema.virtual('fullName').
  2. get(function() { return this.name.first + ' ' + this.name.last; }).
  3. set(function(v) {
  4. this.name.first = v.substr(0, v.indexOf(' '));
  5. this.name.last = v.substr(v.indexOf(' ') + 1);
  6. });
  7. axl.fullName = 'William Rose'; // Now `axl.name.first` is "William"
  • 再次强调,虚拟值不能用于查询和字段选择,因为虚拟值不储存于 MongoDB。

选项

  • Schema有很多可配置选项,你可以在构造时传入或者直接set,选项较多,暂不学习整理。
  1. new Schema({..}, options);
  2. // or
  3. var schema = new Schema({..});
  4. schema.set(option, value);

三、SchemaTypes-模式类型


以下是mongoose的所有合法SchemaTypes:

  • String
  • Boolean
  • Number
  • Array
  • Buffer
  • Date
  • Schema.Types.ObjectId
  • Schema.Types.Mixed
  • Schema.Types.Decimal128

SchemeType选项

  • 你可以直接声明schema type为某一种type,或者赋值一个含有type属性的对象
  1. var schema1 = new Schema({
  2. test: String // `test` is a path of type String
  3. });
  4. var schema2 = new Schema({
  5. test: { type: String } // `test` is a path of type string
  6. });
  • 除了type属性,还可以对这个字段路径指定其它属性,比如在保存之前全部转换为小写
  1. var shema2 = new Schema({
  2. test:{
  3. type:String,
  4. lowercase:true
  5. }
  6. })

全部可用

  • required:布尔值或者函数 如果值为真,为此属性添加require验证器
  • default: 任何值或函数 设置此路径默认值,如果是函数m,函数返回值为默认值
  • select: 布尔值 指定query的默认projections
  • validate: 函数校验
  • get:函数,使用Object.defineProperty()定义自定义getter
  • set:同上
  • alias:别名

索引相关

可以使用 schema type定义索引相关

  • index:布尔值 是否对这个属性创建索引
  • unique:布尔值 是否对这个属性创建唯一索引
  • sparse:布尔值 是否对这个属性创建稀疏索引

四、Connections-连接


  • 可以使用 mongoose.connect()连接MongoDB,默认端口27017

操作缓存

就是说不必等待上面的connect连接成功后,就可以使用创建的 Mongoose models 禁用缓存,要修改 bufferCommands配置,mongoose.set(‘bufferCommands’,fasle)

选项

connect 方法也接受 options 参数,这些参数会传入底层 MongoDB 驱动。

回调

connect()函数接受回调函数,或返回一个Promise

keepAlive

对于长期运行的后台应用,启用毫秒级 keepAlive 是一个精明的操作。不这么做你可能会经常 收到看似毫无原因的 “connection closed” 错误。 mongoose.connect(uri,{keepAlive:120})

五、models-模型


  • Models 是从 Schema 编译来的构造函数。 它们的实例就代表着可以从数据库保存和读取的 documents
  • 从数据库创建和读取 document 的所有操作都是通过 model 进行的。
  1. var schema = new mongoose.Schema({ name: 'string', size: 'string' });
  2. var Tank = mongoose.model('Tank', schema);

上面的参数 Tank是跟model对应的集合(collection)对应的单数形式。 Mongoose会自动找到名称是model的名字的复数形式。 比如上例,Tank这个model对应数据库中tanks这个collection .model()这个函数是对 schema做了拷贝 确保在调用.model()之前把所有需要的东西都加进shema里。

构造documents

  • documents是model的实例,创建谈并保存到数据库非常简单:
  1. const Tank = mongoose.model('Tank',TankSchema)
  2. const small = new Tank({ size:' small'})
  3. small.save(function(err){
  4. if (err) return hanldeError(err)
  5. })
  6. // or
  7. Tank.create({size:'small'},function(err,small){
  8. if (err) return handleError(err)
  9. })

查询

  • 查询文档可以用model的find、findbyId,findOne,和where这些静态方法。

删除

  • model的remove方法可以删除所有匹配查询条件(condition)的文档
  1. Tank.remove({size:small},function(err){
  2. if(err) return handler(err)
  3. })

更新

  • modelupdate 方法可以修改数据库中的文档,不过不会把文档返回给应用层。
  • 如果想更新单独一条文档并且返回给应用层,可以使用 findOneAndUpdate 方法。

六、文档-Documents


  • Mongoose document代表着MongoDB文档的一对一映射。每个document都是他的Model的实例。

更新

使用findById:

  1. Tank.findById(id,function(err,tank){
  2. if (err) return handlerError(err)
  3. tank.size = 'large';
  4. //tank.set({size:'large'})
  5. tank.save(function(err,updateTank){
  6. if (err) return handlerError(err)
  7. res.send(updateTank)
  8. })
  9. })
  • [x] 若仅仅需要更新数据,而不需要获取数据再去更新:

    Tank.update({_id:id},{$set:{size:’large’}},callback)

  • [x] 更新后我们还需要返回这个文档:findByIdAndUpdate

    1. Tank.findByIdAndUpdate(id,{$set:{size:'large'}},{new:true},function(err,tank){
    2. if (err) return handlerError(err)
    3. res.send(tank)
    4. })

    七、子文档-SubDocuments


子文档是指嵌套在另一个文档中的文档。 在Mongoose中,意味着你可以在里嵌套另一个schema。 Mongoose子文档有两种不同的概念:子文档数组和单个嵌套子文档

  1. const chidlSchema = new Schema({name:String})
  2. const parentSchema = new Schema({
  3. children:[childSchema],
  4. child:childSchema
  5. })

子文档与文档的区别是 子文档不能单独保存,他们会在他们的顶级文档保存时保存。

  1. const Parent = mongoose.model('Parent',parentSchema)
  2. const parent = new Parent({children:[{name:'liu'},{name:'ge'},{name:'zhou;}]})
  3. parent.children[0].name = 'liu'
  4. parent.save(callback)

八、Queries 查询


  • Model的多个静态辅助方法都可以查询文档
  • Query实例有一个.then()函数,用法类似Promise

我们看一下demo,查询persons表中name中属性last为Ghost值的文档,只查询 name和occupation两个字段

  1. const Person = mpngoose.model('Pseron',PersonSchema);
  2. Person.findOne({name.last:'Ghost'},'name occupation',function(err,person){
  3. if(err) return handleError(err)
  4. console.log('%s %s is a %s',person.name.fisrt,person.name.last,person.occupation)
  5. })

查询结果的格式取决于做什么操作:

  • findOne()是单个文档
  • find() 是文档列表
  • count() 是文档数量
  • update() 是更新的文档数量

九 中间件—Middleware


  1. 中间件(pre 和 post 钩子)是在异步函数执行时函数传入的控制函数。
  2. Middleware is specified on the shema.
  3. Mongoose4.x有四种中间件:doucument中间件、model中间件、aggregate中间件、query中间件。
  4. document 中间件支持以下document操作:
    1. init
    2. validate
    3. save
    4. remove
  5. query 中间件支持以下 Model 和 Query 操作
    1. count
    2. find
    3. findOne
    4. findOneAndUpdate
    5. findOneAndRemove
    6. updade
  6. aggregate 中间件作用于MyModel.aggregate(),他会在你对 aggregate 对象调用 exec()时执行
    1. aggregate
  7. Model中间件支持以下操作:
    1. insertMany
  8. 所有中间件支持 pre 和 post 钩子。
  9. Query 没有 remove()钩子,只有 docuemnt 有,如果设定了remove钩子,他将会在你调用 myDoc.remove()触发,而不是 myModel.remove(),另外,create()函数会触发 save()钩子。

pre

pre钩子分为『串行』和『并行』两种

**

  • 串行:

    串行中间件一个接一个的执行。具体来说,上一个中间件调用 next 的时候下一个执行

  1. const schema = new Schema(..);
  2. schema.pre('save',function(next){
  3. // to stuff
  4. next()
  5. })

在 mongoose5.x 中,除了手动调用 next 函数,还可以返回一个 Promise,甚至是 async/await。

  1. schema.pre('save',function(){
  2. return doStuff().
  3. then(()=> doMoreStuff())
  4. })
  5. // or
  6. shcema.pre('save',async function(){
  7. await doStuff();
  8. await doMoreStuff();
  9. })
  • 并行

    并行中间件提供细粒度流控制。

  1. const schema = new Schema(..)
  2. shcema.pre('save',true,function(next,done){
  3. next()
  4. setTimeout(done,100)
  5. })

在这个例子中,save 方法将在所有中间件都调用了 done 方法的时候才会执行。 使用场景:

  • 复杂的数据校验
  • 删除依赖文档(删除用户后删除他的所有文档)
  • asynchronous defaults
  • asynchronous tasks that a certain action triggers

Post

Post中间件在方法执行之后调用,这个时候每个 pre 中间件都已完成

  1. schema.post('init',function(doc){
  2. console.log('%s has been initialized from the db', doc._id);
  3. })
  4. schema.post('validate',function(doc){
  5. console.log('%s has been validated (but not saved yet)', doc._id);
  6. })
  7. schema.post('save',function(doc){
  8. console.log('%s has been saved', doc._id);
  9. })
  10. schema.post('remove',function(doc){
  11. console.log('%s has been removed', doc._id);
  12. })
  • 异步 Post 钩子

    如果你给 post 钩子的回调函数传入两个参数,mongoose 会认为第二个参数是 next()函数,可以通过 next 触发下一个中间件

  1. schema.post('save',function(doc,next){
  2. setTimeout(function(){
  3. console.log('pot1')
  4. next()
  5. },100)
  6. })
  7. schema.post('save', function(doc, next) {
  8. console.log('post2');
  9. next();
  10. });
  • Save/Validate钩子

    save()函数触发 validate()钩子,mongoose validate()钩子其实就是 pre(‘save’)钩子,这意味着所有pre(‘validate’)和 post(‘validate’)钩子都会在 pre(‘save’)之前调用。

  • findAndUpdate() 和 Query 中间件使用注意

    pre 和 post save()钩子都不执行于 update()、 findOneAndUpdate()等情况 mongoose4.x为这些函数制定了新钩子

  1. schema.pre('find',function(){
  2. conosle.log(this instanceof mongoose.query) //true
  3. this.start = Date.now()
  4. })
  5. schema.post('find',function(result){
  6. conosle.log(this instanceof mongoose.query) //true
  7. // prints returned documents
  8. console.log('find() returned ' + JSON.stringify(result));
  9. // prints number of milliseconds the query took
  10. console.log('find() took ' + (Date.now() - this.start) + ' millis');
  11. })

错误处理中间件

next() 执行错误时,中间件执行立即停止。但是我们有特殊的 post 中间件技巧处理这个问题 —— 错误处理中渐渐,它可以在出错后执行你指定的代码。 错误处理中间件比普通中间件多一个 error 参数,并且 err 作为第一个参数传入。 而后错误处理中间件可以让你自由地做错误的后续处理

  1. const schema = new Schema({
  2. name:{
  3. type:String,
  4. unique:true
  5. }
  6. })
  7. schema.post('save',function(err,doc,next){
  8. if (error.name === 'MongoError' && error.code === 11000) {
  9. next(new Error('There was a duplicate key error'));
  10. } else {
  11. next(error);
  12. }
  13. })
  14. Person.create([{name:'liu'},{name:'Gezhou'}]);

十、填充—Populate


demo

MongoDb 在 3.2之后,也有像 sql 中的 join 聚合操作,那就死$lookup,而 mongoose 拥有更强大的 populate,可以让你在别的 collection 中引用 document。
Populate 可以自动替换 document 中的指定字段,替换内容从其他 collection 获取,我们填充(populate)单个或者多个 document、单个或者多个对象,甚至是 query 返回的一切对象:

  1. const mongoose = require('mongoose')
  2. const Schema = mongoose.Schema;
  3. const personSchema = Schema({
  4. _id:Schema.types.ObjectId,
  5. name:String,
  6. age:Number,
  7. stories:[{type:Schema.types.ObjectId,ref:'Story'}]
  8. })
  9. const storySchema = Schema({
  10. author:{type:Schema.types.ObjectId,ref:'Person'},
  11. title:String,
  12. fans:[{type:Schema.types.ObjectId,ref:'Person'}]
  13. })
  14. const Story = mongose.model('Story',storySchema)
  15. const Person = mongose.model('Person',personSchema)

我们创建了两个model,Person model中的 stories 字段为 ObjectID 数组,ref 选项告诉mongoose 在填充的时候使用哪个 model,上面的例子就是指 Story 的 model。所有储存在此的_id 都必须是 Story model 中的 document 的 _id

保存 refs

保存 refs 与保存普通属性一样,把_id的值赋给他就好了

  1. const author = new Person({
  2. _id:new mongoose.Types.objectId(),
  3. name:'liugezhou',
  4. age:18
  5. })
  6. author.save(function(err){
  7. if (err) return handleError(err);
  8. const story1 = new Story({
  9. title:'my book',
  10. author:author._id
  11. })
  12. story1.save(function(err){
  13. if (err) return handleError(err);
  14. })
  15. })

Population

  1. Story.
  2. findOne({title:'my book'}).
  3. populate('author').
  4. exec(function(err,story){
  5. if (err) return handleError(err);
  6. console.log('The author is %s', story.author.name);
  7. })

设置被填充字段

mongoose4.0之后,你可以手动填写一个字段

  1. Story.findOne({title:'my book'},function(err,story){
  2. if (err) return handleError(err);
  3. story.author = author
  4. console.log(story.author.name);
  5. })

十一、鉴别器—Discriminator


Discriminator是一种 schema 继承机制。它允许你在相同的底层MongoDb collection上使用部分重叠的 schema 建立多个 model。