深入浅出mongoose

9,386 阅读5分钟

mongoose是nodeJS提供连接 mongodb的一个库,类似于jquery和js的关系,对mongodb一些原生方法进行了封装以及优化。简单的说,Mongoose就是对node环境中MongoDB数据库操作的封装,一个对象模型工具,将数据库中的数据转换为JavaScript对象以供我们在应用中使用。

install mongoose

npm install mongoose --save
const mongoose = require('mongoose'),
      connection = mongoose.connect('mongodb://127.0.0.1:27017/wechatShop', {
        useMongoClient: true
      });

Schema

Mongoose的一切都始于一个Schema。每个schema映射到MongoDB的集合(collection)和定义该集合(collection)中的文档的形式。 Schemas不仅定义了文档和属性的结构,还定义了文档实例方法、静态模型方法、复合索引和文档被称为中间件的生命周期钩子。

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

Models

使用我们的schema定义,我们需要将我们的blogschema转成我们可以用的模型。为此,我们通过mongoose.model(modelName, schema)。

var Blog = mongoose.model('Blog', blogSchema);

实例方法

模型的实例是文档(documents)。文档有许多自己内置的实例方法。我们也可以定义我们自己的自定义文档实例方法。

var Blog = mongoose.model('Blog', blogSchema);
var blog = new Schema({ name: String, type: String });//模型实例

常用的内置实例方法有:

remove、set、get、invalidate、populate、save等

详细api可查阅官方文档内置的实例方法

下面我们来说说自定义实例方法。自定义实例方法,就是对当前实例的methods扩展方法即可,如下所示:

animalSchema.methods.findSimilarTypes = function(cb) {
  return this.model('Animal').find({ type: this.type }, cb);
};

静态方法

常用的内置静态方法有:

create、find、findOne等

给一个模型添加静态方法的也是很简单。继续我们的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的查询。查询辅助方法可以让你扩展mongoose链式查询生成器API。

animalSchema.query.byName = function(name) {
  return this.find({ name: new RegExp(name, 'i') });
};

var Animal = mongoose.model('Animal', animalSchema);
Animal.find().byName('fido').exec(function(err, animals) {
  console.log(animals);
});

索引

mongoose同样也对索引做了处理,在mongoose中定义索引有两种方法。

第一种:直接在schema里面定义,如下所示

var User = mongoose.model('User', {
  username: {
      type: String,
      index: true
  },
  password: String
})

第二种:统一定义索引

var User = mongoose.model('User', {
  username: {
      type: String
  },
  password: String
});

User.index({
    username: 1 / -1  (正向和逆向)
})

关闭索引:

mongoose.connect('mongodb://user:pass@localhost:port/database', { config: { autoIndex: false } });
// or  
mongoose.createConnection('mongodb://user:pass@localhost:port/database', { config: { autoIndex: false } });
// or
animalSchema.set('autoIndex', false);
// or
new Schema({..}, { autoIndex: false });

注意:索引滥用会导致很严重的性能问题,建议合理使用索引。

虚拟属性(针对实例即文档)

虚拟属性 是文档属性,您可以获取和设置但不保存到MongoDB。用于格式化或组合字段,从而制定者去组成一个单一的值为存储多个值是有用的。

下面我们先定义一个实例

// define a schema
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' }
});

假设我们想打印bad的全名。我们可以这样做:

console.log(bad.name.first + ' ' + bad.name.last); // Walter White

或者我们可以在personschema定义 虚拟属性的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

注意,这里的虚拟属性并没有存入数据库,所以如果是直接获取,是获取不到值的。

验证

在我们进入验证语法的细节之前,请记住以下的规则:

验证是在SchemaType定义

验证是中间件。Mongoose寄存器验证作为pre('save')钩子在每个模式默认情况下。

你可以手动使用doc运行验证。validate(callback) or doc.validateSync()。

验证程序不运行在未定义的值上。唯一的例外是required验证器。

验证异步递归;当你调用Model#save,子文档验证也可以执行。如果出现错误,你的 Model#save回调接收它。

验证是可定制的。

 var schema = new Schema({
      name: {
        type: String,
        required: true
      }
    });
    var Cat = db.model('Cat', schema);

    // This cat has no name :(
    var cat = new Cat();
    cat.save(function(error) {
      assert.equal(error.errors['name'].message,
        'Path `name` is required.');

      error = cat.validateSync();
      assert.equal(error.errors['name'].message,
        'Path `name` is required.');
    });
Mongoose有几个内置验证器。

所有的schematypes有内置的require验证器。所需的验证器使用SchemaType的checkrequired()函数确定的值满足所需的验证器。

数值( Numbers )有最大(man)和最小(min)的验证器。

字符串(String)有枚举,match,maxLength和minLength验证器。

每一个上述的验证链接提供更多的信息关于如何使他们和自定义错误信息。

 var breakfastSchema = new Schema({
      eggs: {
        type: Number,
        min: [6, 'Too few eggs'],
        max: 12
      },
      bacon: {
        type: Number,
        required: [true, 'Why no bacon?']
      },
      drink: {
        type: String,
        enum: ['Coffee', 'Tea']
      }
    });
    var Breakfast = db.model('Breakfast', breakfastSchema);

    var badBreakfast = new Breakfast({
      eggs: 2,
      bacon: 0,
      drink: 'Milk'
    });
    var error = badBreakfast.validateSync();
    assert.equal(error.errors['eggs'].message,
      'Too few eggs');
    assert.ok(!error.errors['bacon']);
    assert.equal(error.errors['drink'].message,
      '`Milk` is not a valid enum value for path `drink`.');

    badBreakfast.bacon = null;
    error = badBreakfast.validateSync();
    assert.equal(error.errors['bacon'].message, 'Why no bacon?');
自定义验证

如果内置验证器是不够的话,你可以自定义验证器来适应你的需求。

var userSchema = new Schema({
      phone: {
        type: String,
        validate: {
          validator: function(v) {
            return /\d{3}-\d{3}-\d{4}/.test(v);
          },
          message: '{VALUE} is not a valid phone number!'
        },
        required: [true, 'User phone number required']
      }
    });
验证的错误处理

验证失败后Errors返回一个错误的对象实际上是validatorerror对象。每个ValidatorError有kind, path, value, and message属性。

var Person = db.model('Person', personSchema);
var person = new Person();

var error = person.validateSync();

联表

如果你使用过mysql,肯定用过join,用来联表查询,但mongoose中并没有join,不过它提供了一种更方便快捷的办法,Population。

首先定义两个model。

var User = mongoose.model('User', {
  username: String,
  password: String
})

var News = mongoose.model('News', {
  title: String,
  author: {
    type: mongoose.Schema.ObjectId,
    ref: 'User'
  }
});

然后添加数据

User.create({username:'ck',username:'滴滴'},function(err, data){
  console.log(data)
  News.create({title:'title',author:data},function(){

  })
})

打印查询结果到控制台

News.findOne().populate('author','username').exec(function(err, doc){
  console.log('==============',err, doc)
  //    ============== null { _id: 5a41d2e16f78a43c5873fe03,
  //    title: 'title',
  //    author: { _id: 5a41d2e16f78a43c5873fe02, username: 'ck' 
  //    },
  //    __v: 0 }
})
欢迎各路大佬对文章错误的地方进行指正,万分感谢,共同学习进步。博主的GitHub地址,可以在GitHub提Issues或者直接在文章下面评论区留言。