티스토리 뷰

728x90
반응형
RDBMS 와 다르게 몽고디비는 JOIN을 할 수 없을까?
populate 가 뭐지?  몽고디비에서의 JOIN과 같은건가?

 

몽고디비(Document based NoSQL) 에서는 SQL의 JOIN 와 똑같은 기능이 없어도

다른 콜렉션의 다큐먼트 데이터를 불러서 사용해야되는 상황이 필요하기에 유사한 기능은 존재한다. 

NodeJS 환경에서는 Mongoose를 사용하게되는데 Mongoose는 populate() 라는 함수가  그렇다.

 

Mongoose 다큐먼트를 읽어보고 이해하는 것을 추천한다.

 

Mongoose v6.7.2: Query Population

MongoDB has the join-like $lookup aggregation operator in versions >= 3.2. Mongoose has a more powerful alternative called populate(), which lets you reference documents in other collections. Population is the process of automatically replacing the specifi

mongoosejs.com

(Population 정의)
Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s). We may populate a single document, multiple documents, a plain object, multiple plain objects, or all objects returned from a query.


특정한 경로(path)에 따라 다른 콜렉션의 다큐먼트로 대체하여 자동으로 명시하도록 해주는 것 이라고 하는데
두개의 콜렉션이 참조컬럼을 통해서 다른 다큐먼트로 대체하는 함수이다.

 

// populate 를 하기 전

상품다큐먼트: { 
 _id: "6370b031271d61db9e798969",
 user: "636b85f635551795415e29fc", // 유저의 _id 를 참조한다.
 name: "블랙 패딩",
 price: 55000
}


유저다큐먼트: {
  _id: "636b85f635551795415e29fc"
  name: "기스깅"
}
// populate 후 상품데이터

상품다큐먼트: { 
 _id: "6370b031271d61db9e798969",
 user: {
   _id: "636b85f635551795415e29fc",
   name: "기스깅"
 }
 name: "블랙 패딩",
 price: 55000
}

 

 

여기서 유의해야할점은

populate() 함수는 Join처럼 DB자체에서 합쳐주진 않으며 

계속 중첩되면 성능이 저하될 수 있는 문제가 발생할 수 있다고하니 필요할 때만 쓰자.

 


populate 하다가 겪은 문제점 - StrictPopulateError

StrictPopulateError: Cannot populate path `User`because it is not in your schema. Set the `strictPopulate` option to false to override.

 

StrictPopulateError 는 어떤 에러일까?

물론 StrictPopulate 옵션을 fasle로 설정할 수 있기도하지만

구글링해서 해결해본 결과...

 다른 콜렉션을 참조하는  컬럼이 존재하지 않거나

참조컬럼은 명시했어도  참조컬럼 이름이 아닌 다른 이름으로 populate를 했을때 발생하는 에러이다.

 

[문제 발생코드]

- products.repository.ts

async findOnePopulated(productId: string) {
   return await this.productModel.find().populate('users').exec();
}

 

- 상품모델(문제는 아니지만 문제 발생 당시 정의한 코드)

// src/products/schemas/product.schema.ts


import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import * as mongoose from 'mongoose';
import { User, UserDocument } from '../../users/schemas/user.schema';

export type ProductDocument = Product & mongoose.Document;

@Schema()
export class Product {
  /**
   * 물건:셀러 = N:1
   * 셀러: User 중 isSeller가 true 인 회원을 의미합니다.
   */

  @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' })
  user: User;

	...
}

export const ProductSchema = SchemaFactory.createForClass(Product);

 


나의 해결법

 

[상품모델]

// src/products/schemas/product.schema.ts


import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import * as mongoose from 'mongoose';
import { User, UserDocument } from '../../users/schemas/user.schema';

export type ProductDocument = Product &
  mongoose.Document & { user: UserDocument }; 

@Schema()
export class Product {
  /**
   * 물건:셀러 = N:1
   * 셀러: User 중 isSeller가 true 인 회원을 의미합니다.
   */

  @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' })
  user: User;

	...
}

export const ProductSchema = SchemaFactory.createForClass(Product);

 

 

[사용자 모델]

- 몽고디비의 모든 콜렉션은 _id 컬럼을 갖고있다.

그러므로 스키마정의할 때 _id를 primary key로 하고싶다면 _id 를 따로 정의할 필요는 없다.

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import mongoose from 'mongoose';

export type UserDocument = User & mongoose.Document;

@Schema()
export class User {
  @Prop({ required: true })
  name: string; // 이름

  ...
}

export const UserSchema = SchemaFactory.createForClass(User);

 

 

1-1단계) user: User

user 필드의 타입을 User(사용자 모델) 로 했다.

 

1-2단계) Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' }) 

'User의 primary key인 _id 를 참조함' 을 명시하는 옵션을 부여했다.

상품모델(Product)에서  user 필드는 사용자모델(User) 의 key(필드명: _id)를 참조하는 필드이다. 

RDBMS로 말하자면 외래키처럼, Product의 user 필드는 User(사용자 모델)을 참조하는 외래키이다.

 

2단계) export type ProductDocument = Product & mongoose.Document & {   user : UserDocument  }

Product(상품모델) 가 User 와 populate하려면 type에 추가해야한다.

user 라는 키워드로 UserDocument와 populate 연산을 함을 정의했다.

그렇다면 키워드는 무엇일까? 즉 User 콜렉션을 참조하는 외래키 user 이다.

 

 

아래 Product와 User을 populate한 결과이다.

populate( populate 키워드 , [ 내가 select할 필드명 ] )

// src/products/products.repository.ts

@Injectable()
export class ProductsRepository {
 constructor(
  @Injectable(Product.name)
  private readonly productModel: Model<ProductDocument>
 ){}
 
 ...
 
 async findOnePopulated(productId: string) {
    const productOne = await this.productModel
      .findOne({ _id: productId, deletedAt: null })
      .populate('user', [ // 비밀번호를 제외한 판매자 정보를 가져왔다.
        '_id',
        'name',
        'email',
        'phoneNumber',
        'isSeller',
        'sellerNickname',
      ])
      .exec();
    return productOne;
  }
}

 

 

[결론]

(1-1 ~ 1-2단계만 해도) populate() 호출시 참조컬럼 이름만 입력해도 동작이 잘된다! 

async findOnePopulated(productId: string) {
    // 참조컬럼명이 user 이므로, 'user' 을 참조해서 
    // _id에 해당하는 User 다큐먼트를 가져오겠다는 의미이다.
    return await this.productModel.find().populate('user').exec();
 }

[참고]

1. population 

 

Mongoose Populate 사용하기

Mongoose Populate 사용하기

gwanwoodev.github.io

 

 

https://www.zerocho.com/category/MongoDB/post/59a66f8372262500184b5363

 

www.zerocho.com

 

2. nestjs + mongoose populate

 

Cannot populate when I use Foreign key in mongoose

I tried to join three documents in mongodb using mongoose in nodejs, but unfortunately this error occurs. My mongoose version is 6.2.3 Declaring Schema const mongoose = require('mongoose') const

stackoverflow.com

 

728x90
반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함