Mongoose populate, virtual 사용방법

Casin·2022년 10월 19일
2

Mongoose(MongoDB)

목록 보기
3/3
post-thumbnail

populate?

Population은 문서의 지정된 경로를 다른 컬렉션의 문서로 자동 교체하는 프로세스입니다.
단일 문서, 여러 문서, 일반 개체, 여러 일반 개체 또는 쿼리에서 반환된 모든 개체를 채울 수 있습니다.

populate의 참고사항

  • 문서가 없다면 필드가 null로 반환됨
  • populate는 여러번 사용 가능함
  • 두개의 동일한 필드의 populate를 수행하면 두번째 작업이 첫번째 작업을 재정의함

예시

예시 코드는 예전에 공부할때 사용했던 영화 리뷰 등록 프로젝트로 사용하였음.

/model/review.js
const mongoose = require('mongoose'); // Erase if already required

var reviewSchema = new mongoose.Schema({
    comment: String,
    score: {
        type: Number,
        min: 1,
        max: 5
    },
    target: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "Movie"
    },
    targetstr: {
        type: String,
    },
    targetCd: {
        type: String,
    },
    createdAt: {
        type: Date,
        default: Date.now()
    }
});

module.exports = mongoose.model('Review', reviewSchema);
/model/movie.js
const mongoose = require('mongoose');

var movieSchema = new mongoose.Schema({
    movieCd: String,
    movieNm: String,
    movieNmEn: String,
    prdtYear: String,
    openDt: String,
    typeNm: String,
    prdtstatNm: String,
    nationAlt: String,
    genreAlt: String,
    repNationNm: String,
    repGenreNm: String,
    directors: [{ peopleNm: String }],
    companys: [{ companyCd: String, companyNm: String }],
});

module.exports = mongoose.model('Movie', movieSchema);
/app.js
const mongoose = require("mongoose");
const Movie = require("./model/movie");
const Review = require("./model/review");
const url = "mongodb+srv://mongoDB_URI";

!async function () {
    await mongoose.connect(url, { dbName: "example" });

    let reviews = await Review.find({}).populate("target");
    //let reviews = await Review.find({}).populate("", "movieNm repGenreNm");
    // path 다음 인자에 select도 사용 가능함.
    console.log(reviews);
}();
출력 예시
{
    _id: new ObjectId("62e74a33b6b80650d6d56174"),
    comment: '더빙 너무 잘됐어요',
    score: 4,
    target: {
      _id: new ObjectId("62e1da422e7c1c96838d3cb6"),
      movieCd: '20226107',
      movieNm: '극장판 도라에몽: 진구의 우주소전쟁 리틀스타워즈 2021',
      movieNmEn: 'Doraemon the Movie: Nobita’s Little Star Wars 2021',
      prdtYear: '2021',
      openDt: '20220803',
      typeNm: '장편',
      prdtStatNm: '개봉예정',
      nationAlt: '일본',
      genreAlt: '애니메이션',
      repNationNm: '일본',
      repGenreNm: '애니메이션',
      directors: [Array],
      companys: []
    },
    targetCd: '20226107',
    createdAt: 2022-08-01T03: 32: 48.248Z,
    __v: 0
}

virtual?

populate를 그냥 사용하게 되면 기본적으로 대상 ref모델의 _id로 매칭이 되는데 이것을 변경하고 싶다면 virtual을 사용해야 한다.

예시

예시 코드는 예전에 공부할때 사용했던 영화 리뷰 등록 프로젝트로 사용하였음.

/model/review.js
const mongoose = require('mongoose'); // Erase if already required

var reviewSchema = new mongoose.Schema({
	...	
});

// virtual을 이용해서 populate용 가상 변수를 설정할 수 있음.
reviewSchema.virtual("vtTargetCd", {
    localField: "targetCd", // 비교할 로컬 필드
    ref: "Movie", // 비교해야하는 컬렉션
    foreignField: "movieCd", // 비교해야하는 컬렉션의 필드
    // justOne: true // 한개의 행만 받아와야 할때 사용
})

module.exports = mongoose.model('Review', reviewSchema);
/app.js
const mongoose = require("mongoose");
const Movie = require("./model/movie");
const Review = require("./model/review");
const url = "mongodb+srv://mongoDB_URI";

!async function () {
    await mongoose.connect(url, { dbName: "example" });

    let reviews = await Review.find({}).populate("vtTargetCd").lean();
    console.dir(reviews, { depth: 3 });
}();
출력 예시
{
    _id: new ObjectId("62e74a33b6b80650d6d56174"),
    comment: '더빙 너무 잘됐어요',
    score: 4,
    target: new ObjectId("62e1da422e7c1c96838d3cb6"),
    targetCd: '20226107',
    createdAt: 2022-08-01T03:32:48.248Z,
    __v: 0,
    vtTargetCd: [
      {
        _id: [ObjectId],
        movieCd: '20226107',
        movieNm: '극장판 도라에몽: 진구의 우주소전쟁 리틀스타워즈 2021',
        movieNmEn: 'Doraemon the Movie: Nobita’s Little Star Wars 2021',
        prdtYear: '2021',
        openDt: '20220803',
        typeNm: '장편',
        prdtStatNm: '개봉예정',
        nationAlt: '일본',
        genreAlt: '애니메이션',
        repNationNm: '일본',
        repGenreNm: '애니메이션',
        directors: [Array],
        companys: []
      }
    ]
}

populate의 다중사용, match, select

populate는 체이닝으로 여러번 사용이 가능하다.
populate에 match를 사용하여 조건을 걸어줄 수 있다.
populate에 select를 사용하여 원하는 필드의 값을 가져오거나 제외할 수 있다.

예시

예시 코드는 학원에서 팀 프로젝트로 만들었던 가게 예약 시스템 서버단 코드 일부분을 발췌

/model/rstr.js
import mongoose from 'mongoose';

let rstrSchema = new mongoose.Schema({
    RSTR_ID: Number,
    RSTR_NM: String,
    BSNS_STATM_BZCND_NM: String,
})

rstrSchema.virtual("rstrImg", { // 가게 이미지 가져오기
    localField: "RSTR_NM",
    ref: "rstr_img",
    foreignField: "RSTR_NM",
    justOne: true
});

rstrSchema.virtual("getMenu", { // 가게 메뉴 가져오기
    localField: "RSTR_ID",
    ref: "menu",
    foreignField: "RSTR_ID",
});

export default mongoose.model('rstr', rstrSchema, "rstr");
/router/api.js
import express from 'express';

import RSTR from '../model/rstr.js' // mongo 식당 기본 정보
import RSTR_OPRT from '../model/rstr_oprt.js' // 식당 운영 정보
import RSTR_IMG from '../model/rstr_img.js' // 식당 이미지 정보
import MENU from '../model/menu.js' // 식당 메뉴 정보

const router = express.Router();
router
	...

    // 메뉴 검색 해서 식당 가져오기
    .get("/getSearchMenu", async (req, res) => {
        console.log(req.query);

        // 해당 메뉴가 있는 식당의 ID를 가져와서 중복 제거하고 가게 정보 가져오기
        MENU.find({ MENU_NM: { $regex: req.query.menu } }).distinct("RSTR_ID").then((result) => {
            // 중복 제거한 식당ID 값으로 메뉴 불러오기
            RSTR.find({ RSTR_ID: result }).populate({
                path: "getMenu",
                match: { MENU_NM: { $regex: req.query.menu } },
                select: "-_id -RSTR_ID MENU_NM",
            }).populate("rstrImg").lean().then((result) => {
                res.status(200).json({ result: true, length: result.length, datas: result });
            })
        }).catch((err) => {
            res.status(501).json({ result: false, message: err });
        });
    })
export default router;
출력 예시
{
    "_id": "633fa0e0590e2837ff5ba360",
    "RSTR_ID": 1243,
    "RSTR_NM": "왕자관",
    "RSTR_RDNMADR": "광주광역시 동구 충장로 101-6",
    "RSTR_LNNO_ADRES": "광주광역시 동구 충장로1가 8-2",
    "RSTR_LA": "35.1472485",
    "RSTR_LO": "126.9182939",
    "RSTR_TELNO": "062-222-3344",
    "BSNS_STATM_BZCND_NM": "중국식",
    "BSNS_LCNC_NM": "일반음식점",
    "RSTR_INTRCN_CONT": "\"왕자관\"은 광주광역시 동구에 있는 맛집으로, 가장 가까운 지하철역은 문화전당역(구도청)입니다. 50년 이상의 전통을 자랑합니다.",
    "getMenu": [
        {
            "MENU_NM": "유니짜장"
        },
        {
            "MENU_NM": "간짜장"
        }
    ],
    "rstrImg": {
        "_id": "633fa198590e2837ff5bbaf3",
        "RSTR_ID": 1243,
        "RSTR_NM": "왕자관",
        "AREA_NM": "광주광역시 동구",
        "RSTR_IMG_URL": "https://ukcooyocdlvo8099722.cdn.ntruss.com/public_data/2021-12/restaurant/875538_1565136201.jpg"
    }
}
profile
그저 그런 개발 교육생

0개의 댓글