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 被已牆了。)