mongoose.js 基础使用——Schemas

MongoDB是由C++语言编写的非关系型数据库,是一个基于分布式文件储存的开源数据库系统,其内容存储形式类似JSON对象,它的字段值可以包含其他文档、数组及文档数组,非常灵活,和JavaScript可以说是很般配了。

小试牛刀

MongoDB分为三个概念:数据库,集合,文档。mongoose操作数据需要先定义Schema(集合的约束),将要连接的集合加以定义好的约束,便形成Model构造函数,利用model我们可以轻松的操作集合里的Document

let mongoose = require('mongoose')
mongoose.connect('mongodb://localhost/music')
let db = mongoose.connection;
// 第二个参数为函数,所以使用bind包装一下
db.on('error', console.error.bind(console, 'connection error;'));

// 创建集合约束
let ownSongsSchema = mongoose.Schema({
  songTitle: String,
  album: { type: String, default: '未知专辑' },
  duration: String,
  singer: String,
  issueDate: Date
}, {
  timestamps: {
    createdAt: 'createTime', updatedAt: 'updateTime',
    currentTime: () => Date.now()
  }
})
// 将集合加以约束,形成数据模型
let OwnSongs = mongoose.model('ownSongs', ownSongsSchema)
// 利用数据模型插入一条文档
let songs = new OwnSongs({
  songTitle: '一路向北',
  ablum: '11月的萧邦',
  duration: '04:55',
  singer: '周杰伦',
  issueDate: '2005-11-01',
})
// 保存到数据库中
songs.save(function (err, info) {
  console.log(err, info);
})

Schemas

每个schema都会映射一个MongoDB collection,并定义这个collection里的文档的构成。
document 里每个属性的类型都会被转换为 在 blogSchema 里定义对应的 SchemaType。
允许使用的 SchemaTypes 有:

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

Schema的功能不只是定义文档结构和属性类型,它可以定义——

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

创建一个 model

通过 mongoose.model函数将Schema转换为一个Model

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

实例方法(method)

documents 是 Models 的实例。 Document 有很多自带的实例方法, 当然也可以自定义我们自己的方法。

// 定义一个 schema
  var animalSchema = new Schema({ name: String, type: String });

  // 为animalSchema的“methods”对象添加一个函数
  animalSchema.methods.findSimilarTypes = function(cb) {
    return this.model('Animal').find({ type: this.type }, cb);
  };

现在所有 animal 实例都有 findSimilarTypes 方法:

  var Animal = mongoose.model('Animal', animalSchema);
  var dog = new Animal({ type: 'dog' });

  dog.findSimilarTypes(function(err, dogs) {
    console.log(dogs); // woof
  });

注意,不要在schema上挂在mongoose已经有的方法,且挂在方法不要使用箭头函数,否则调用时会导致this指向错误

静态方法(static)

继续用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);
  });

查询助手(query helper)

查询助手作用于 query 实例,方便你自定义拓展你的链式查询

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

索引(index)

用来提高查询性能

虚拟值(Virtuals)

Virtualsdocument 的属性,但是不会被保存到 MongoDBgetter 可以用于格式化和组合字段数据, setter 可以很方便地分解一个值到多个字段。

 // 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 axl = new Person({
    name: { first: 'Axl', last: 'Rose' }
  });

如果你要log出全名,可以这么做:

console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose

但是每次都这么拼接实在太麻烦了, 推荐你使用 virtual property getter, 这个方法允许你定义一个 fullName 属性,但不必保存到数据库。

personSchema.virtual('fullName').get(function () {
  return this.name.first + ' ' + this.name.last;
});
console.log(axl.fullName); // Axl Rose

如果对 document 使用 toJSON()toObject(),默认不包括虚拟值, 你需要额外向 toObject() 或者 toJSON() 传入参数 { virtuals: true }
你也可以设定虚拟值的 setter ,下例中,当你赋值到虚拟值时,它可以自动拆分到其他属性:

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'; // Now `axl.name.first` is "William"

模式类型(SchemaTypes)

SchemaType 处理字段路径各种属性的定义,以下是 mongoose 的所有合法 SchemaTypes:

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

示例:

var schema = new Schema({
  name:    String,
  binary:  Buffer,
  living:  Boolean,
  updated: { type: Date, default: Date.now },
  age:     { type: Number, min: 18, max: 65 },
  mixed:   Schema.Types.Mixed,
  _someId: Schema.Types.ObjectId,
  decimal: Schema.Types.Decimal128,
  array:      [],
  ofString:   [String],
  ofNumber:   [Number],
  ofDates:    [Date],
  ofBuffer:   [Buffer],
  ofBoolean:  [Boolean],
  ofMixed:    [Schema.Types.Mixed],
  ofObjectId: [Schema.Types.ObjectId],
  ofArrays:   [[]],
  ofArrayOfNumbers: [[Number]],
  nested: {
    stuff: { type: String, lowercase: true, trim: true }
  }
})

// example use

var Thing = mongoose.model('Thing', schema);

var m = new Thing;
m.name = 'Statue of Liberty';
m.age = 125;
m.updated = new Date;
m.binary = new Buffer(0);
m.living = false;
m.mixed = { any: { thing: 'i want' } };
m.markModified('mixed');
m._someId = new mongoose.Types.ObjectId;
m.array.push(1);
m.ofString.push("strings!");
m.ofNumber.unshift(1,2,3,4);
m.ofDates.addToSet(new Date);
m.ofBuffer.pop();
m.ofMixed = [1, [], 'three', { four: 5 }];
m.nested.stuff = 'good';
m.save(callback);
SchemaType 选项

你可以直接声明 schema type 为某一种 type,或者赋值一个含有 type 属性的对象,除此之外,还可以对字段路径指定其他属性,例如你要在保存之前要把字母都改成小写:

var schema2 = new Schema({
  test: {
    type: String,
    lowercase: true // Always convert `test` to lowercase
  }
});

lowercase 属性只作用于字符串。以下有一些全部 type 可用的选项和一些限定部分 type 使用的选项。

全部可用

  • required: 布尔值或函数 如果值为真,为此属性添加 required 验证器
  • default: 任何值或函数 设置此路径默认值。如果是函数,函数返回值为默认值
  • select: 布尔值 指定 query 的默认projections
  • validate: 函数 adds a validator function for this property
  • get: 函数 使用 Object.defineProperty()定义自定义 getter
  • set: 函数 使用 Object.defineProperty()定义自定义 setter
  • alias: 字符串 仅mongoose >= 4.10.0。 为该字段路径定义虚拟值 gets/sets

索引相关
你可以使用 schema type 选项定义MongoDB indexes。

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

String

  • lowercase: 布尔值 是否在保存前对此值调用 .toLowerCase()
  • uppercase: 布尔值 是否在保存前对此值调用 .toUpperCase()
  • trim: 布尔值 是否在保存前对此值调用 .trim()
  • match: 正则表达式 创建验证器检查这个值是否匹配给定正则表达式
  • enum: 数组 创建验证器检查这个值是否包含于给定数组

Number

  • min: 数值 创建验证器检查属性是否大于或等于该值
  • max: 数值 创建验证器检查属性是否小于或等于该值

Date

  • min: Date
  • max: Date
使用注意

Dates
内建 Date 方法 mongoose 修改跟踪逻辑,因此保存不到数据库。

Mixed
一个啥都可以放的 SchemaType , 虽然便利,但也会让数据难以维护。 Mixed 可以通过 Schema.Types.Mixed 或 传入一个空对象定义。以下三种方法效果一致:

var Any = new Schema({ any: {} });
var Any = new Schema({ any: Object });
var Any = new Schema({ any: Schema.Types.Mixed });

因为这是个 schema-less type, 所以你可以赋值为任意类型, 但是 mongoose 无法自动检测并保存你的修改。 要告诉 Mongoose 你修改了 Mixed type 的值,调用 文档的 .markModified(path) 方法, 传入你的 Mixed 字段路径。

person.anything = { x: [3, 4, { y: "changed" }] };
person.markModified('anything');
person.save(); // anything will now get saved

Arrays
创造 SchemaTypes 或子文档数组。

var ToySchema = new Schema({ name: String });
var ToyBox = new Schema({
  toys: [ToySchema],
  buffers: [Buffer],
  string:  [String],
  numbers: [Number]
  // ... etc
});

数组的默认值是 [] (空数组)。

var Toy = mongoose.model('Test', ToySchema);
console.log((new Toy()).toys); // []

要手动把默认值设置为 undefined,从而覆盖 []

var ToySchema = new Schema({
  toys: {
    type: [ToySchema],
    default: undefined
  }
});

选项

Schemas 有很多可配置选项,你可以在构造时传入或者直接 set

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

合法的选项有:

  • autoIndex 用来提高查询性能

  • bufferCommands 文件传输时中断提示

  • capped 数据库大小限制

  • collection Mongoose 通过 utils.toCollectionName 方法 默认生成 collection 的名称(生成 model 名称的复数形式)。 设置这个选项可以自定义名称。

  • id Mongoose 会默认生成一个虚拟值 id,指向文档的 _id 字段。 如果你不需要 id 虚拟值,可以通过这个选项禁用此功能。

  • _id Mongoose 默认给你的 Schema 赋值一个 _id。 这个值的类型是 ObjectId,这与MongoDB的默认表现一致。 如果你不需要 _id,可以通过这个选项禁用此功能。

  • minimize 配置是否保存空对象,默认不保存空对象

  • read 用于连接时的别名

  • shardKey 分片相关

  • strict 默认不能 save schema 里没有声明的属性

  • strictQuery strict 模式不适用于查询的filter参数。

  • toJSON 下文中解释

  • toObject 下文中解释

  • typeKey 在schema里type字段被视为关键字

  • validateBeforeSave 对插入的文档进行检查

  • versionKey 在document里__v字段被视为关键字

  • collation 为查询(query和) 聚合(aggregation)设置 collation

  • skipVersioning 跳过版本控制

  • timestamps 下文中解释

  • useNestedStrict mongoose 会忽略嵌套的 strict 设定。

toObject

Documents 的 toObject 方法可以把文档转换成一个 plain javascript object (也就是去掉里面的方法)。 这是一个可以接收多个参数的方法,我们可以在 schemas 定义这些参数。

例如要打印出虚拟值,可以向 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' }
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' }
timestamps

如果设置了 timestamps 选项, mongoose 会在你的 schema 自动添加 createdAtupdatedAt 字段, 其类型为 Date。

这两个字段的默认名是 createdAtupdatedAt, 你可以通过设定 timestamps.createdAttimestamps.updatedAt 自定义字段名称。

且,可指定存储值

var thingSchema = new Schema({..}, { timestamps: {
  createdAt: 'createTime',
  updatedAt: 'updateTime',
  currentTime: () => Date.now()
} });
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing();
thing.save(); // `createTime` & `updateTime` 以时间戳的形式存储

你可能感兴趣的:(mongoose.js 基础使用——Schemas)