用过 MongoDB 的人应该都知道它是没有关系型数据库里的 join 特性的,这意味着当我们使用 MongoDB 读取某个 document 及其关联的 document 的字段的时候,变得尤为麻烦。
基于此,Mongoose 封装了一个 population 的功能,当你在定义 Schema 的时候指定了某个 field 是引用了另一个 Schema ,那么你在获取 document 的时候就可以使用 populate 方法让 Mongoose 帮你通过引用 Schema 和 id 找到关联的另一个 document,并且用该 document 的内容替换掉原来引用字段的内容,使引用的 ducoment 使用起来就像是内嵌的 document 一样方便。
首先,如果要使用这个功能,必须给你的“外键字段”定义 Mongoose 的“外键约束”(这里借用关系型数据库的术语,只是为了帮助理解)。假设我们有一个用户的 Schema ,其中有一个字段是followings,保存用户关注了的其他用户,类似微博。Mongoose 的 Schema 定义如下:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var UserSchema = Schema({
name : String,
followings : [{ type: Schema.Types.ObjectId, ref: 'User' }]
});
var User = mongoose.model('User', UserSchema);
然后我们创建两个 User 实例,并且先将其中之一保存进数据库(为了产生 _id ):
var lily = new User({name: 'lily'})
,lucy = new User({name: 'lucy'});
lily.save(cb);
lily 保存成功后,我们再在回调函数 cb 中保存 lucy ,不过这个时候假设 lucy 关注了 lily:
function cb(err) {
if(err) throw err;
lucy.followings.push(lily._id);// line 1
lucy.save(cb1);
}
这样就保存完成了。不过好像我们没用引用字段之前也是这么保存的吧?有没有更直接一点的?有,你其实可以将 line 1 的语句改成以下形式, Mongoose 也会自动帮你做内部解析的:
lucy.followings.push(lily);
这样看起来代码就更接近自然语言了,有点ORM的味道。
当然,上面的例子可能对开发者来说没有什么太大的用处,那么就来看看 population 在获取 document 的时候能给我们带来什么好处吧。
假设我们现在要找到 lucy 的信息以及她所关注的用户,我们可以这样:
User.findOne({name: 'lucy'}).exec(cb2);
但是这样拿出来的 lucy 的 followings 是一组存储了 ObjectId 的数组,还得去一个个查找对应的用户,多麻烦啊。所以这个时候用 population 就简单多了,直接指定你想要 populate 哪个引用字段:
User.findOne({name: 'lucy'}).populate('followings').exec(cb2);
这样的话,在回调函数 cb2 中你得到的就是一个存储了若干个 User 对象的数组了。
populate 方法可以用在 document 上、 model 上或者是 query 对象上,这意味着你几乎可以在任何地方调用这个方法以填充你的引用字段。
当然,populate 方法在不同对象上参数不大一样,但是都接收一个option的参数,你可以用这些参数指定:
目前,Mongoose 只支持以下几种引用字段的类型:
而且自然而然,引用 document 的主键类型必须和引用字段类型相对应。在生产环境中,推荐主键类型和引用类型都使用 ObjectId ,一是因为 ObjectId 不包含业务含义,二是 ObjectId 不大可能重复,三是因为 Mongoose 默认生成的主键类型就是 ObjectId ,可以减少很多配置的操作。
更多信息请关注这里。