mongoose填充(populate)

当需要在一个集合中引用另一个集合的文档时,就需要使用到mongoose的填充功能。所谓的填充是指首先为当前集合文档的某个字段引用另一个集合的文档,通常是引用_id,然后再将被引用的文档填充 进原文档中。
下面讲讲常见的用法:

用法

基础用法

假设现在要实现一个评论的集合,这个集合中包含author字段需要引用user-info集合中对应用户的文档信息,那么首先我们需要通过ref来建立联系。
  • ref: 要引用的文档所在的集合的名称
  • type: 字段类型,可以是其他类型,但是通常会是ObjectId,因为通常是根据_id来找到对应要引用的文档。
export const CommentSchema = new mongoose.Schema( { author: { ref: 'user_info', type: mongoose.Types.ObjectId, }, }, ); export const commentModel = mongoose.model('comments', CommentSchema);
然后我们在创建comment文档的时候,需要将author文档的_id存进去。
// 假设这是user_info集合中,评论作者的文档 /* { "_id" : ObjectId("6318650a866c3556e01c49fd"), "email" : "123@qq.com", "password" : "123", "role" : "管理员", "__v" : 0 } */ commentModel.create({ author: ObjectId("6318650a866c3556e01c49fd") })
注意author存的是ObjectId,这也就是为什么前面的type一般是写ObjectId。
而此时你的文档是这样的
{ _id: ObjectId("62d2dd84054bb169ad1efdd9"), author: ObjectId("6318650a866c3556e01c49fd") }
如果你直接查询,那么author仍然只是一条ObjectId,要想把它变成user_info集合下对应的文档信息,你就需要执行populate进行填充。populate传递的第一个参数是要填充的路径,这个例子里是author。
commentModel.find({_id: ObjectId("62d2dd84054bb169ad1efdd9")}) .populate('author') // 返回的结果是 { _id: ObjectId("62d2dd84054bb169ad1efdd9"), author: { "_id" : ObjectId("6318650a866c3556e01c49fd"), "email" : "123@qq.com", "password" : "123", "role" : "管理员", "__v" : 0 } }
这样就实现了mongoose的填充效果。

只填充特定字段

有时候我们并不需要user_info文档的所有信息,例如上个例子中我们只需要用户的email字段。那么可以这样写:
commentModel.find({_id: ObjectId("62d2dd84054bb169ad1efdd9")}) .populate('author','email') // 也可以这样写 commentModel.find({_id: ObjectId("62d2dd84054bb169ad1efdd9")}) .populate({ path: 'author', select: 'email' })

匹配规则

我们可以设置某些条件,只有满足这些条件才会填充。
commentModel.find({_id: ObjectId("62d2dd84054bb169ad1efdd9")}) .populate({ path: 'author', select: 'email', match: { email: "123@qq.com" } })
在上面的例子中,mongodb会检查被引用文档的email字段,只有当email字段等于"123@qq.com"时才会被填充。这个例子看起来没什么用,但是在实际开发中你可以使用$lt等关键字来扩展使用场景。

填充多个字段

path传递一个数组即可。
commentModel.find({_id: ObjectId("62d2dd84054bb169ad1efdd9")}) .populate({ path: ['author', '其他要填充的字段名'], })

填充数组

如果要填充的对象是一个包含多个文档的数组,那么直接对这个数组调用populate即可。
如果填充字段是一个嵌套结构,则可以用.符号
const post = await postModel.find({_id: ObjectId("62d2dd84054bb169ad1efd11")}) await post.populate({ path: 'comments.author', });
 

判断是否已填充及取消填充

你可以使用populated函数,如果该函数返回一个真值,那么就表示该字段已经被填充,否则说明该字段并未被填充。
story.populated('author'); // truthy story.depopulate('author'); // Make `author` not populated anymore story.populated('author'); // undefined
取消填充需要使用depopulate函数,示例如上👆

限制填充总数和限制每文档填充数

当填充字段的个数过多时就会大幅度影响性能,因此我们有时需要限制填充的个数,mongoose提供limit选项来选择填充的总数,注意这里limit是设置填充的总数,而非每个文档填充的个数,如果要设置每个文档填充的个数,你需要使用perDocumentLimit ,下面这个例子体现了两者的区别。
Story.create([ { title: 'Casino Royale', fans: [1, 2, 3, 4, 5, 6, 7, 8] }, { title: 'Live and Let Die', fans: [9, 10] } ]); // 当使用limit时 const stories = await Story.find().populate({ path: 'fans', options: { limit: 2 } }); stories[0].name; // 'Casino Royale' stories[0].fans.length; // 2 // 2nd story has 0 fans! stories[1].name; // 'Live and Let Die' stories[1].fans.length; // 0 // 使用perDocumentLimit时 const stories = await Story.find().populate({ path: 'fans', // Special option that tells Mongoose to execute a separate query // for each `story` to make sure we get 2 fans for each story. perDocumentLimit: 2 }); stories[0].name; // 'Casino Royale' stories[0].fans.length; // 2 stories[1].name; // 'Live and Let Die' stories[1].fans.length; // 2

参考

上面的示例只涉及到基础的用法,有关填充的更多细节可以去看官方文档,文档链接如下: