Skip to content

Mongoose에서 참조 사용 방법(SQL의 Join처럼): Populate

Jin Kwan Kim edited this page Dec 1, 2020 · 1 revision

MongoDB version 3.2 이상부터 join과 비슷한 $lookup 집계 연산자를 지원하고 있다. 이와 같은 기능으로 Mongoose는 populate()를 지원한다. populate()는 다른 collecition에 있는 documents를 참조할 수 있도록 한다.

Population은 document에서 다른 collection의 document에 대한 경로를 자동으로 대체하는 절차이다. query로 단일 document부터 multipule document, single & multiple plain object 뿐만 아니라 모든 object를 받아올 수 있다.

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);

ref option은 population 동안에 어떤 모델을 사용할지를 나타낸다. 타입은 별 다른 이유가 없으면 ObjectId를 사용하길 권장한다.

Saving refs

저장할 때는 다른 property를 저장할 때와 동일하고, 단지 _id 값만 할당해 주면 된다.

const author = new Person({
  _id: new mongoose.Types.ObjectId(),
  name: 'Ian Fleming',
  age: 50
});

author.save(function (err) {
  if (err) return handleError(err);

  const story1 = new Story({
    title: 'Casino Royale',
    author: author._id    // assign the _id from the person
  });

  story1.save(function (err) {
    if (err) return handleError(err);
    // that's it!
  });
});

Population

Story.
  findOne({ title: 'Casino Royale' }).
  populate('author').
  exec(function (err, story) {
    if (err) return handleError(err);
    console.log('The author is %s', story.author.name);
    // prints "The author is Ian Fleming"
  });

Populate할 때 원래의 _id를 명시해주지 않아도 mongoose가 자동으로 그 값을 document로 반환해 준다.

Setting Populated Fields

직접 populate할 property를 지정할 수도 있다. document는 ref가 참조하고 있는 model의 instance이어야만 한다.

Story.findOne({ title: 'Casino Royale' }, function(error, story) {
  if (error) {
    return handleError(error);
  }
  story.author = author;
  console.log(story.author.name); // prints "Ian Fleming"
});

Checking Wheter a Field is Populated

populated() 함수를 호출해서 field가 populate됐는지 확인할 수 있다. truthy value를 반환하면 populate된 것이다.

story.populated('author'); // truthy

story.depopulate('author'); // Make `author` not populated anymore
story.populated('author'); // undefined

Mongoose가 자동으로 _id에 대해서는 getter를 추가하기 때문에 populate되지 않아도 _id를 불러올 수 있다.

story.populated('author'); // truthy
story.author._id; // ObjectId

story.depopulate('author'); // Make `author` not populated anymore
story.populated('author'); // undefined

story.author instanceof ObjectId; // true
story.author._id; // ObjectId, because Mongoose adds a special getter

What If There's No Foreign Document?

일치하는 document가 없다면 null을 반환한다. 일반적인 SQL에서의 left join과 비슷하게 동작한다고 보면 된다.

await Person.deleteMany({ name: 'Ian Fleming' });

const story = await Story.findOne({ title: 'Casino Royale' }).populate('author');
story.author; // `null`

schema에서 array로 정의했다면 empty array를 반환한다.

Field Selection

특정 field만 받아오고 싶다면 populate() 메소드의 두번째 인자에 값을 넣어주면 된다.

Story.
  findOne({ title: /casino royale/i }).
  populate('author', 'name'). // only return the Persons name
  exec(function (err, story) {
    if (err) return handleError(err);

    console.log('The author is %s', story.author.name);
    // prints "The author is Ian Fleming"

    console.log('The authors age is %s', story.author.age);
    // prints "The authors age is null'
  });

Populate Multiple Paths

여러 참조된 document들을 불러오고 싶다면

Story.
  find(...).
  populate('fans').
  populate('author').
  exec();

같은 경로에 대해서 여러번 부르면 마지막 요청만 유효하다.

Query conditions and other options

특정 옵션에 맞는 document만 불러 오려면

Story.
  find().
  populate({
    path: 'fans',
    match: { age: { $gte: 21 } },
    // Explicitly exclude `_id`, see http://bit.ly/2aEfTdB
    select: 'name -_id'
  }).
  exec();

방금의 경우에서 match 옵션으로 Story document를 걸러낼 수는 없다. 만약 매칭되는 document가 없다면 fans에 빈 배열이 있는 Story document가 반환된다.

const story = await Story.
  findOne({ title: 'Casino Royale' }).
  populate({ path: 'author', name: { $ne: 'Ian Fleming' } }).
  exec();
story.author; // `null`

만약 author name으로 필터링을 하고 싶으면 denormalization 이용해야한다.

출처

🔗 https://mongoosejs.com/docs/populate.html

Clone this wiki locally