Mongoose 使用 Population 填充'关联表'数据

MongooseMongoDBODM(Object Document Mapper)

  • 什么是 ODM ? 其实和 ORM(Object Relational Mapper) 是同类型的工具。都是将数据库的数据转化为代码对象的库,使用转化后的对象可以直接对数据库的数据进行 CRUD(增删改查)
  • MongoDB 是文档型数据库(Document Database),不是关系型数据库(Relational Database)。而 Mongoose 可以将 MongonDB 数据库存储的文档(documents)转化为 javascript 对象,然后可以直接进行数据的增删改查。

因为 MongoDB 是文档型数据库,所以它没有关系型数据库[ joins ]( http://zh.wikipedia.org/wiki/%E8%BF%9E%E6%8E%A5_(SQL) (数据库的两张表通过"外键",建立连接关系。) 特性。也就是在建立数据的关联时会比较麻烦。为了解决这个问题, Mongoose 封装了一个 Population 功能。使用 Population 可以实现在一个 document 中填充其他 collection(s)document(s)

在定义 Schema 的时候,如果设置某个 field 关联另一个 Schema ,那么在获取 document 的时候就可以使用 Population 功能通过关联 Schema 的 field 找到关联的另一个 document,并且用被关联 document 的内容替换掉原来关联字段(field)的内容。

接下来分享下: Query#populate Model#populate Document#populate 的用法

先建立三个 SchemaModel :

javascriptvar 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 才有效)。
  • Post 的属性 poster comments 分别关联 UserComment
  • Comment 的属性 post commenter 分别关联 PostUser
  • 三个 Models 的关系:一个 user-- has many -->post。一个 post-- has one -->user, has many -->comment。一个 comment-- has one -->post 和 user。

创建一些数据到数据库:

javascript// 连接数据库
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 字段:
javascript//填充所有 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 字段:
javascriptPost.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 字段:
javacriptPost.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 字段:
javascriptUser.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 被已墙了。)

参考

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章