Mongoose 是 MongoDB 的 ODM(Object Document Mapper)。javascript
什么是ODM? 其实和ORM(Object Relational Mapper)是同类型的工具。都是将数据库的数据转化为代码对象的库,使用转化后的对象能够直接对数据库的数据进行CRUD(增删改查)。html
MongoDB
是文档型数据库(Document Database),不是关系型数据库(Relational Database)。而Mongoose
能够将 MongonDB 数据库存储的文档(documents)转化为 javascript 对象,而后能够直接进行数据的增删改查。java
由于MongoDB
是文档型数据库,因此它没有关系型数据库joins
(数据库的两张表经过"外键",创建链接关系。) 特性。也就是在创建数据的关联时会比较麻烦。为了解决这个问题,Mongoose
封装了一个Population
功能。使用Population
能够实如今一个 document 中填充其余 collection(s) 的 document(s)。git
在定义Schema
的时候,若是设置某个 field 关联另外一个Schema
,那么在获取 document 的时候就可使用 Population 功能经过关联Schema
的 field 找到关联的另外一个 document,而且用被关联 document 的内容替换掉原来关联字段(field)的内容。github
Query#populate
Model#populate
Document#populate
的用法先创建三个Schema
和Model
:mongodb
var mongoose = require('mongoose'); var Schema = mongoose.Schema; var UserSchema = new Schema({ name : { type: String, unique: true }, posts : [{ type: Schema.Types.ObjectId, ref: 'Post' }] }); var User = mongoose.model('User', UserSchema); var PostSchema = new Schema({ poster : { type: Schema.Types.ObjectId, ref: 'User' }, comments : [{ type: Schema.Types.ObjectId, ref: 'Comment' }], title : String, content : String }); var Post = mongoose.model('Post', PostSchema); var CommentSchema = new Schema({ post : { type: Schema.Types.ObjectId, ref: "Post" }, commenter : { type: Schema.Types.ObjectId, ref: 'User' }, content : String }); var Comment = mongoose.model('Comment', CommentSchema);
在上述的例子中,建立了三个 Models:User
,Post
,Comment
。数据库
User
的属性 posts,对应是一个 ObjectId 的数组。ref
表示关联Post
(注意: 被关联的model的 type 必须是 ObjectId, Number, String, 和 Buffer
才有效)。api
Post
的属性 poster 和 comments 分别关联User
和Comment
。数组
Comment
的属性 post 和 commenter 分别关联Post
和User
。app
三个 Models 的关系:一个 user--has many-->post。一个 post--has one-->user,has many-->comment。一个 comment--has one-->post 和 user。
建立一些数据到数据库:
// 链接数据库 mongoose.connect('mongodb://localhost/population-test', function (err){ if (err) throw err; createData(); }); function createData() { var userIds = [new ObjectId, new ObjectId, new ObjectId]; var postIds = [new ObjectId, new ObjectId, new ObjectId]; var commentIds = [new ObjectId, new ObjectId, new ObjectId]; var users = []; var posts = []; var comments = []; users.push({ _id : userIds[0], name : 'aikin', posts : [postIds[0]] }); users.push({ _id : userIds[1], name : 'luna', posts : [postIds[1]] }); users.push({ _id : userIds[2], name : 'luajin', posts : [postIds[2]] }); posts.push({ _id : postIds[0], title : 'post-by-aikin', poster : userIds[0], comments : [commentIds[0]] }); posts.push({ _id : postIds[1], title : 'post-by-luna', poster : userIds[1], comments : [commentIds[1]] }); posts.push({ _id : postIds[2], title : 'post-by-luajin', poster : userIds[2], comments : [commentIds[2]] }); comments.push({ _id : commentIds[0], content : 'comment-by-luna', commenter : userIds[1], post : postIds[0] }); comments.push({ _id : commentIds[1], content : 'comment-by-luajin', commenter : userIds[2], post : postIds[1] }); comments.push({ _id : commentIds[2], content : 'comment-by-aikin', commenter : userIds[1], post : postIds[2] }); User.create(users, function(err, docs) { Post.create(posts, function(err, docs) { Comment.create(comments, function(err, docs) { }); }); }); }
数据的准备就绪后,接下来就是探索populate
方法:
什么Query? Query(查询),能够快速和简单的从MongooDB查找出相应的 document(s)。 Mongoose 封装了不少查询的方法,使得对数据库的操做变得简单啦。这里分享一下populate
方法用法。
语法:
**`Query.populate(path, [select], [model], [match], [options])`**参数:
path
类型:String
或Object
。
String
类型的时, 指定要填充的关联字段,要填充多个关联字段能够以空格分隔。
Object
类型的时,就是把 populate 的参数封装到一个对象里。固然也能够是个数组。下面的例子中将会实现。
select
类型:Object
或String
,可选,指定填充 document 中的哪些字段。
Object
类型的时,格式如:{name: 1, _id: 0}
,为0表示不填充,为1时表示填充。
String
类型的时,格式如:"name -_id"
,用空格分隔字段,在字段名前加上-
表示不填充。详细语法介绍 query-select
model
类型:Model
,可选,指定关联字段的 model,若是没有指定就会使用Schema
的ref
。
match
类型:Object
,可选,指定附加的查询条件。
options
类型:Object
,可选,指定附加的其余查询选项,如排序以及条数限制等等。
填充User
的posts
字段:
//填充全部 users 的 posts User.find() .populate('posts', 'title', null, {sort: { title: -1 }}) .exec(function(err, docs) { console.log(docs[0].posts[0].title); // post-by-aikin }); //填充 user 'luajin'的 posts User.findOne({name: 'luajin'}) .populate({path: 'posts', select: { title: 1 }, options: {sort: { title: -1 }}}) .exec(function(err, doc) { console.log(doc.posts[0].title); // post-by-luajin }); //这里的 populate 方法传入的参数形式不一样,其实实现的功能是同样的,只是表示形式不同。
填充Post
的poster
和comments
字段:
Post.findOne({title: 'post-by-aikin'}) .populate('poster comments', '-_id') .exec(function(err, doc) { console.log(doc.poster.name); // aikin console.log(doc.poster._id); // undefined console.log(doc.comments[0].content); // comment-by-luna console.log(doc.comments[0]._id); // undefined }); Post.findOne({title: 'post-by-aikin'}) .populate({path: 'poster comments', select: '-_id'}) .exec(function(err, doc) { console.log(doc.poster.name); // aikin console.log(doc.poster._id); // undefined console.log(doc.comments[0].content); // comment-by-luna console.log(doc.comments[0]._id); // undefined }); //上两种填充的方式实现的功能是同样的。就是给 populate 方法的参数不一样。 //这里要注意,当两个关联的字段同时在一个 path 里面时, select 必须是 document(s) //具备的相同字段。 //若是想要给单个关联的字段指定 select,能够传入数组的参数。以下: Post.findOne({title: 'post-by-aikin'}) .populate(['poster', 'comments']) .exec(function(err, doc) { console.log(doc.poster.name); // aikin console.log(doc.comments[0].content); // comment-by-luna }); Post.findOne({title: 'post-by-aikin'}) .populate([ {path:'poster', select: '-_id'}, {path:'comments', select: '-content'} ]) .exec(function(err, doc) { console.log(doc.poster.name); // aikin console.log(doc.poster._id); // undefined console.log(doc.comments[0]._id); // 会打印出对应的 comment id console.log(doc.comments[0].content); // undefined });
Model(模型),是根据定义的 Schema 编译成的抽象的构造函数。models 的实例 documents,能够在数据库中被保存和检索。数据库全部 document 的建立和检索,都经过 models 处理。
语法:
**`Model.populate(docs, options, [cb(err,doc)])`**参数:
docs
类型:Document
或Array
。单个须要被填充的 doucment 或者 document 的数组。
options
类型:Object
。以键值对的形式表示。
keys:path
select
match
model
options
,这些键对应值的类型和功能,与上述Query#populate
方法的参数相同。
[cb(err,doc)]
类型:Function
,回调函数,接收两个参数,错误err
和填充完的doc(s)
。
填充Post
的poster
和comments
字段以及comments
的commenter
字段:
Post.find({title: 'post-by-aikin'}) .populate('poster comments') .exec(function(err, docs) { var opts = [{ path : 'comments.commenter', select : 'name', model : 'User' }]; Post.populate(docs, opts, function(err, populatedDocs) { console.log(populatedDocs[0].poster.name); // aikin console.log(populatedDocs[0].comments[0].commenter.name); // luna }); });
Document,每一个 document 都是其 Model 的一个实例,一对一的映射着 MongoDB 的 document。
语法:
**`Document.populate([path], [callback])`**参数:
path
类型:String
或Object。与上述
Query#populate`方法的 path 参数相同。
callback
类型:Function
。回调函数,接收两个参数,错误err
和填充完的doc(s)
。
填充User
的posts
字段:
User.findOne({name: 'aikin'}) .exec(function(err, doc) { var opts = [{ path : 'posts', select : 'title' }]; doc.populate(opts, function(err, populatedDoc) { console.log(populatedDoc.posts[0].title); // post-by-aikin }); });
博文涉及的完整例子在 gist 上。(ps: gist 被已墙了。)