mongoose之Population

在MongoDB中没有关系型数据库的join特性,所以在document进行相互关联的时候就比较麻烦。

在mongoose中有一个population用于解决这类问题。在定义Schema的时候,如果设置某个field关联另一个Schema,那么在获取document的时候就可以使用Population功能通过关联Schemafield找到关联的另一个document,并且用被关联document的内容替换掉原来关联field的内容。

例子

下面我拿官方的例子来说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var personSchema = Schema({
_id: Schema.Types.ObjectId,
name: String,
age: Number,
stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});

var storySchema = Schema({
author: { type: Schema.Types.ObjectId, ref: 'Person' },
title: String,
fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});

var Story = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);

首先先定义两个model,并且这两个model的schema相互引用。可以看到在person的stories字段(field )中我们定义了数组,表示关联的数据解析为数组。其中ref选项告诉mongoose在使用populate的时候使用什么model,并且后续所有的传入的_id都要在这个model之中。

保存数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var author = new Person({
_id: new mongoose.Types.ObjectId(),
name: 'Ian Fleming',
age: 50
});

author.save(function (err) {
if (err) return handleError(err);

var story1 = new Story({
title: 'Casino Royale',
author: author._id // assign the _id from the person
});

story1.save(function (err) {
if (err) return handleError(err);
// thats it!
});
});

保存数据的时候并没有什么不同,只是记得要分配下_id,若是_ids,则把所有需要展现的document的_id做成一个数组传入。

解析出引用

上面做的一些操作并没有什么特别的,但接下来我们可以看到通过populate我们解析除了内嵌的关联document。

1
2
3
4
5
6
7
8
Story.
findOne({ title: 'Casino Royale' }).
populate('author').
exec(function (err, story) {
if (err) return handleError(err);
console.log('The author is %s', story.author.name);
// prints "The author is Ian Fleming"
});

下面我说下populate方法的常用用法

Query#populate

就是查询语句之后可以调用populate

语法

1
**`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,如果没有指定就会使用Schema的ref。

  • match

类型:Object,可选,指定附加的查询条件。

  • options

类型:Object,可选,指定附加的其他查询选项,如排序以及条数限制等等。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//填充所有 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 方法传入的参数形式不同,其实实现的功能是一样的,只是表示形式不一样。

Model#populate

model直接调用populate

语法

1
**`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)。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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#populate

document直接调用populate

语法

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

参数

  • path

类型:String或Object。与上述Query#populate`方法的 path 参数相同。

  • callback

类型:Function。回调函数,接收两个参数,错误err和填充完的doc(s)。

例子

1
2
3
4
5
6
7
8
9
10
11
12
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
});
});

跨越等级的Populate

populate可以进行多重引用

1
2
3
4
5
6
7
8
9
10
11
12
var userSchema = new Schema({
name: String,
friends: [{ type: ObjectId, ref: 'User' }]
});

User.
findOne({ name: 'Val' }).
populate({
path: 'friends',
// Get friends of friends - populate the 'friends' array for every friend
populate: { path: 'friends' }
});

参考

官方starter

Mongoose 之 Population 使用