안녕하세요. 이번 시간에는 몽구스의 편리한 기능 중 하나인 populate에 대해 알아보겠습니다.
몽고DB를 사용하다보면 하나의 다큐먼트가 다른 다큐먼트의 ObjectId를 쓰는 경우가 있습니다. 그럴 때 그 ObjectId를 실제 객체로 치환하는 작업이 필요합니다. 예시를 들어보겠습니다.
User 컬렉션
{
_id: { $oid: '574e9c0f9f663a1700fbe06e' },
name: 'zero',
friends: [{ $oid: '59a66f8372262500184b5363' }, { $oid: '574e8f46c9100617001c9cb9' }],
bestFriend: { $oid: '574e8f46c9100617001c9cb9' }
}
{
_id: { $oid: '574e8f46c9100617001c9cb9' },
name: 'hero',
friends: [{ $oid: '59a66f8372262500184b5363' }, { $oid: '574e9c0f9f663a1700fbe06e' }],
bestFriend: { $oid: '59a66f8372262500184b5363' }
}
{
_id: { $oid: '59a66f8372262500184b5363' },
name: 'nero',
friends: [{ $oid: '574e9c0f9f663a1700fbe06e' }, { $oid: '574e8f46c9100617001c9cb9' }],
bestFriend: { $oid: '574e9c0f9f663a1700fbe06e' }
}
이렇게 세 명의 친구가 있다고 칩시다. 친구와 베프 관계가 위와 같이 나와 있습니다. 만약 zero를 불러온다면
db.users.findOne({ name: 'zero' })
이렇게 해야 합니다. 결과는 당연히 아래와 같겠죠.
{
_id: { $oid: '574e9c0f9f663a1700fbe06e' },
name: 'zero',
friends: [{ $oid: '59a66f8372262500184b5363' }, { $oid: '574e8f46c9100617001c9cb9' }],
bestFriend: { $oid: '574e8f46c9100617001c9cb9' }
}
하지만 friends랑 bestFriend 부분이 ObjectId라서 이게 누군지 쉽게 알 수 없습니다. 각각 조회를 해서 합쳐주어야 합니다.
이를 편하게 해주는 기능이 populate입니다. 먼저 schema의 필드가 다음과 같이 되어있어야 합니다.
const userSchema = new mongoose.Schema({
name: String,
friends: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
bestFriend: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
});
mongoose.model('User', userSchema);
ref에 해당 ObjectId가 속해있는 모델을 넣어줍니다. 현재는 자기 자신(User)을 가리키지만 다른 컬렉션 모델이어도 상관없습니다.
User.findOne({ name: 'zero' }).populate('bestFriend').exec((err, data) => {
console.log(data);
}); // 또는 populate({ path: 'bestFriend' })도 가능
이렇게 하면 data가
{
_id: { $oid: '574e9c0f9f663a1700fbe06e' },
name: 'zero',
friends: [{ $oid: '59a66f8372262500184b5363' }, { $oid: '574e8f46c9100617001c9cb9' }],
bestFriend: {
_id: { $oid: '574e8f46c9100617001c9cb9' },
name: 'hero',
friends: [{ $oid: '59a66f8372262500184b5363' }, { $oid: '574e9c0f9f663a1700fbe06e' }],
bestFriend: { $oid: '59a66f8372262500184b5363' }
}
}
위와 같이 됩니다. bestFriend 부분의 ObjectId가 실제 객체로 치환되었습니다. populate할 때 bestFriend의 name과 bestFriend만 보고 싶다면
User.findOne({ name: 'zero' }).populate('bestFriend', 'name bestFriend').exec((err, data) => {
console.log(data);
});
populate의 두 번째 인자로 넣어주면 됩니다. 프로젝션이라고 생각하시면 되겠습니다.
{
_id: { $oid: '574e9c0f9f663a1700fbe06e' },
name: 'zero',
friends: [{ $oid: '59a66f8372262500184b5363' }, { $oid: '574e8f46c9100617001c9cb9' }],
bestFriend: {
_id: { $oid: '574e8f46c9100617001c9cb9' },
name: 'hero',
bestFriend: { $oid: '59a66f8372262500184b5363' }
}
}
한 번에 friends와 bestFriend 모두 치환하는 것도 가능합니다. populate를 연달아 쓰면 됩니다.
User.findOne({ name: 'zero' }).populate('bestFriend').populate('friends').exec((err, data) => {
console.log(data);
});
{
_id: { $oid: '574e9c0f9f663a1700fbe06e' },
name: 'zero',
friends: [{
_id: { $oid: '59a66f8372262500184b5363' },
name: 'nero',
friends: [{ $oid: '574e9c0f9f663a1700fbe06e' }, { $oid: '574e8f46c9100617001c9cb9' }],
bestFriend: { $oid: '574e9c0f9f663a1700fbe06e' }
}, {
_id: { $oid: '574e8f46c9100617001c9cb9' },
name: 'hero',
bestFriend: { $oid: '59a66f8372262500184b5363' }
}],
bestFriend: {
_id: { $oid: '574e8f46c9100617001c9cb9' },
name: 'hero',
bestFriend: { $oid: '59a66f8372262500184b5363' }
}
}
모든 $oid가 실제 다큐먼트로 바뀌었습니다. 여기서 끝이 아닙니다. 위의 결과를 보시면 populate한 객체 안에 $oid가 들어있습니다. 이것도 바꿀 수 있습니다. populate를 중첩한다고 표현합니다. bestFriend의 bestFriend를 찾아보겠습니다. 아래와 같이 옵션을 주면 됩니다. 참고로 select 속성은 프로젝션 필드입니다.
User.findOne({ name: 'zero' }).populate({
path: 'bestFriend',
populate: { path: 'bestFriend', select: 'name friends' },
}).exec((err, data) => {
console.log(data);
});
{
_id: { $oid: '574e9c0f9f663a1700fbe06e' },
name: 'zero',
friends: [{ $oid: '59a66f8372262500184b5363' }, { $oid: '574e8f46c9100617001c9cb9' }],
bestFriend: {
_id: { $oid: '574e8f46c9100617001c9cb9' },
name: 'hero',
friends: [{ $oid: '59a66f8372262500184b5363' }, { $oid: '574e9c0f9f663a1700fbe06e' }],
bestFriend: {
_id: { $oid: '59a66f8372262500184b5363' },
name: 'nero',
bestFriend: { $oid: '574e9c0f9f663a1700fbe06e' }
}
}
}
zero의 bestFriend로 hero, hero의 bestFriend로 nero가 생성되었습니다. 정말 편하죠?
하지만 populate를 맹신해서는 안 됩니다. populate는 $oid로 모두 조회를 해서 자바스크립트 단에서 합쳐주는 것이지 JOIN처럼 DB 자체에서 합쳐주는 것이 아닙니다. 따라서 성능이 그렇게 좋지는 않습니다. 특히 populate가 중첩되면 성능 문제가 생길 확률이 커집니다.