Mongoose 之 Population 使用

MongooseMongoDBODM(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的用法

先創建三個SchemaModel: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:UserPostComment數據庫

  • User 的屬性 posts,對應是一個 ObjectId 的數組。ref表示關聯Post(注意: 被關聯的model的 type 必須是 ObjectId, Number, String, 和 Buffer 纔有效)。api

  • Post的屬性 postercomments 分別關聯UserComment數組

  • Comment的屬性 postcommenter 分別關聯PostUserapp

  • 三個 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方法:

1. Query#populate

什麼Query? Query(查詢),能夠快速和簡單的從MongooDB查找出相應的 document(s)。 Mongoose 封裝了不少查詢的方法,使得對數據庫的操做變得簡單啦。這裏分享一下populate方法用法。

語法:

**`Query.populate(path, [select], [model], [match], [options])`**

參數:

path

  類型:StringObject
  String類型的時, 指定要填充的關聯字段,要填充多個關聯字段能夠以空格分隔。
  Object類型的時,就是把 populate 的參數封裝到一個對象裏。固然也能夠是個數組。下面的例子中將會實現。

select

  類型:ObjectString,可選,指定填充 document 中的哪些字段。
  Object類型的時,格式如:{name: 1, _id: 0},爲0表示不填充,爲1時表示填充。
  String類型的時,格式如:"name -_id",用空格分隔字段,在字段名前加上-表示不填充。詳細語法介紹 query-select

model

  類型:Model,可選,指定關聯字段的 model,若是沒有指定就會使用Schemaref

match

  類型:Object,可選,指定附加的查詢條件。

options

  類型:Object,可選,指定附加的其餘查詢選項,如排序以及條數限制等等。

  • 填充Userposts字段:

//填充全部 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 方法傳入的參數形式不一樣,其實實現的功能是同樣的,只是表示形式不同。
  • 填充Postpostercomments字段:

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

2. Model#populate

Model(模型),是根據定義的 Schema 編譯成的抽象的構造函數。models 的實例 documents,能夠在數據庫中被保存和檢索。數據庫全部 document 的建立和檢索,都經過 models 處理。

語法:

**`Model.populate(docs, options, [cb(err,doc)])`**

參數:

docs

  類型:DocumentArray。單個須要被填充的 doucment 或者 document 的數組。

options

  類型:Object。以鍵值對的形式表示。
  keys:path select match model options,這些鍵對應值的類型和功能,與上述Query#populate方法的參數相同。

[cb(err,doc)]

  類型:Function,回調函數,接收兩個參數,錯誤err和填充完的doc(s)

  • 填充Postpostercomments字段以及commentscommenter字段:

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

3. Document#populate

Document,每一個 document 都是其 Model 的一個實例,一對一的映射着 MongoDB 的 document。

語法:

**`Document.populate([path], [callback])`**

參數:

path

  類型:StringObject。與上述Query#populate`方法的 path 參數相同。

callback

  類型:Function。回調函數,接收兩個參數,錯誤err和填充完的doc(s)

  • 填充Userposts字段:

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

參考

原文連接

相關文章
相關標籤/搜索