Populate trong Mongoose - NodeJS căn bản
Trong bài viết này chúng ta sẽ cùng nhau đi tìm hiểu về Populdate trong Mongoose. Trong phiên bản từ 3.2 trở đi Mongose có hỗ trợ một aggregation operator hỗ trợ việc join các document có mối liên hệ với nhau là lockup. Nhưng phổ biến và mạnh mẽ hơn đó chính là populate(), cho phép bạn join các ...
Trong bài viết này chúng ta sẽ cùng nhau đi tìm hiểu về Populdate trong Mongoose.
Trong phiên bản từ 3.2 trở đi Mongose có hỗ trợ một aggregation operator hỗ trợ việc join các document có mối liên hệ với nhau là lockup
. Nhưng phổ biến và mạnh mẽ hơn đó chính là populate()
, cho phép bạn join các document từ các collections khác.
Populate là quá quá trình tự động thay thế các paths trong documents gốc bằng cách documents trong các documents khác. Chúng ta có thể gộp một hay nhiều document, objects hay tất cả object từ một query. Bên dưới là ví dụ cụ thể:
const mongoose = require('mongoose'); const Schema = mongoose.Schema; const personSchema = Schema({ _id: Schema.Types.ObjectId, name: String, age: Number, stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }] }); const storySchema = Schema({ author: { type: Schema.Types.ObjectId, ref: 'Person' }, title: String, fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }] }); const Story = mongoose.model('Story', storySchema); const Person = mongoose.model('Person', personSchema);
Bên trên chúng ta có đã khởi tạo 2 Models. Person
sẽ có một trường lưu trữ stories
là một array ObjectID. Thuộc tính ref
được sử dụng để cho Mongoose biết rằng trường này được join đến model nào, trong trường hợp này là model Story
. Tất cả _ids
mà chúng ta lưu trữ phải là _ids
của một document trong Story
model.
Chú ý: ObjectId, Number, String, và Buffer đều sử dụng được options ref
. Tuy nhiên bạn nên sử dụng ObjectID ngoại trư trường hợp đặc biệt.
1. Lưu một refs
Bên trên chúng ta đã khởi tạo 2 Model, bây giờ mình sẽ thêm vào Person
một document và đồng nghĩa với nó mình cũng thêm một document trong Story
.
const author = new Person({ _id: new mongoose.Types.ObjectId(), name: 'Nguyễ Du', age: 0 }); author.save(function (err) { if (err) return handleError(err); const story1 = new Story({ title: 'Truyện Kiều', author: author._id // Lấy giá trị của ._id cho refs }); story1.save(function (err) { if (err) return handleError(err); //Lưu thành công !!! }); });
2. Population
Tiếp theo, sau khi thêm một refs
bạn có thể thực hiện gộp documents trong Story
vào Person
quá trình này gọi là populate
, cũng gần tương tự như join trong MySQL. Bên dưới mình có ví dụ cụ thể :
Story. findOne({ title: 'Truyện Kiều' }). populate('author'). exec(function (err, story) { if (err) return handleError(err); console.log('Tác giả %s', story.author.name); // prints "Tác giả Nguyễn Du" });
Tùy chọn các trường
Chúng ta cũng có thể tùy chỉnh các trường được trả về để tiết kiệm tài nguyên, ví dụ bên dưới mình sẽ chỉ trả về giá trị author.name
mà thôi.
Story. findOne({ title: 'Truyện Kiều' }). populate('author', 'name'). // Chỉ trả về Person name exec(function (err, story) { if (err) return handleError(err); console.log('Tác giả : %s', story.author.name); // prints "Tác giả : Nguyễn Du" console.log('Tuổi %s', story.author.age); // prints "Tuổi : null' });
Populate với nhiều Paths
Ngoài ra, chúng ta còn có thể populate với nhiều path khác nhau bằng cú pháp sau:
Story. find(...). populate('fans'). populate('author'). exec();
Thêm điều kiện truy vấn
Bạn có thể thêm điều kiện truy vấn, trong ví dụ trên mình sẽ truy vấn fans
dựa vào tuổi lớn hơn 50:
Story. find(...). populate({ path: 'fans', match: { age: { $gte: 50 } }, select: 'name -_id' }). exec();
Ngoài ra populate còn hỗ trợ giới hạn các document, thêm object options như ví dụ:
const stories = Story.find().sort({ name: 1 }).populate({ path: 'fans', options: { limit: 2 } //giới hạn các document }); stories[0].fans.length; // 2 stories[1].fans.length; // 0
Nếu bạn muốn giới hạn trong mỗi document thì bạn có thể sử dụng perDocumentLimit
:
const stories = await Story.find().sort({ name: 1 }).populate({ path: 'fans', perDocumentLimit: 2 }); stories[0].fans.length; // 2 stories[1].fans.length; // 2
Populate với nhiều cấp
Ở đây mình có một Model như sau:
var userSchema = new Schema({ name: String, friends: [{ type: ObjectId, ref: 'User' }] });
Giả sử bạn muốn lấy tất cả bạn bè của một người nào đó, rồi lại lấy bạn bè của những người vừa lấy được =))) Chúng ta sẽ sử dụng cú pháp sau:
User. findOne({ name: 'Val' }). populate({ path: 'friends', // Get friends of friends - populate the 'friends' array for every friend populate: { path: 'friends' } });
refPath
Mongoose cũng có thể populate từ nhiều collections khác nhau dựa trên giá trị của thuộc tính trong document. Giả sử bạn đang xây dựng một Schema để lưu trữ bình luận. Người dùng có thể comment về bài đăng trên Blog hoặc Sản phẩm
const commentSchema = new Schema({ body: { type: String, required: true }, on: { type: Schema.Types.ObjectId, required: true, refPath: 'onModel' }, onModel: { type: String, required: true, enum: ['BlogPost', 'Product'] } }); const Product = mongoose.model('Product', new Schema({ name: String })); const BlogPost = mongoose.model('BlogPost', new Schema({ title: String })); const Comment = mongoose.model('Comment', commentSchema);
Lúc này khi lưu một comment bạn chỉ cần chọn Model mà bạn muốn dùng để populate
:
const book = await Product.create({ name: 'Zaidap.com }); const post = await BlogPost.create({ title: 'Lập trình NodeJS' }); const commentOnBook = await Comment.create({ body: 'Sách hay', on: book._id, onModel: 'Product' }); const commentOnPost = await Comment.create({ body: 'Thông tin rất hữu ích', on: post._id, onModel: 'BlogPost' }); const comments = await Comment.find().populate('on').sort({ body: 1 }); comments[0].on.name; // "Zaidap.com" comments[1].on.title; // "Lập trình NodeJS"
Populate trong Middleware
Tham khảo thêm bài viết về Middleware. Bạn có thể thực hiện thao tác populate trong pre và posts hooks. Chúng ta có một vài ví dụ :
// Thường dùng `populate()` khi `find()` được gọi MySchema.pre('find', function() { this.populate('user'); });
// Thường gọi `populate()` sau khi `find()` được gọi. // Rât hữu ích trong trường hợp bạn muốn populate sau khi tìm được documents MySchema.post('find', async function(docs) { for (let doc of docs) { if (doc.isPublic) { await doc.populate('user').execPopulate(); } } });
// `populate()` sau khi save. // Hưu ích cho việc gửi populate về API MySchema.post('save', function(doc, next) { doc.populate('user').execPopulate().then(function() { next(); }); });
Trên đây là những kiến thức cơ bản về Populate trong Mongoose. Mong bài viết này có thể giúp ích cho bạn cho việc lập trình với NodeJS, cảm ơn bạn đã quan tâm bài viết này.