게시글

강좌14 - MongoDB - 3년 전 등록

Mongoose(몽구스) populate

안녕하세요. 이번 시간에는 몽구스의 편리한 기능 중 하나인 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가 중첩되면 성능 문제가 생길 확률이 커집니다.

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.

댓글

5개의 댓글이 있습니다.
4달 전
감사합니다 이해 잘 되었어요!! 그런데..... zero 베스트 프렌드는 hero인데 hero 베스트 프렌드는 zero가 아니네요... 글 보다 갑자기 슬퍼졌음..
일 년 전
글 잘 읽었습니당
3년 전
정말 좋은 정보 감사드립니다.
populate를 사용해서 데이터를 가져오는 것에는 성공했는데,
그 데이터를 웹에 보내서 웹에서 console.log()를 해 보면 undefind로 나오네요
어떻게 해야 populate된 객체의 값을 보낼 수 있을까요??
3년 전
서버에서 콘솔로그찍으면 되는데 웹에서는 안된다는 거죠? 데이터를 웹에 보낼때 json으로 보내는 게 아닌건가요?
3년 전
감사합니다. 좋은 정보 잘 배웠습니다.
3년 전
정말 감사합니다 이런 기능이 있는지 모르고 사용하고 있었네요