인프런 커뮤니티 질문&답변

Haewoong Lee님의 프로필 이미지

작성한 질문수

[코드캠프] 부트캠프에서 만든 고농축 백엔드 코스

복합키 설정 관련 질문

해결된 질문

작성

·

288

0

안녕하세요.

항상 친절하고 상세한 답변 감사합니다.

복합키 생성 관련 오류가 있어 질문드립니다.

@Entity()
@ObjectType()
export class Dibs {
    @ManyToOne(() => Member)
    @PrimaryColumn()
    @JoinColumn({ name: 'memberId' })
    @Field(() => Member)
    member: Member;

    @ManyToOne(() => Campground)
    @PrimaryColumn()
    @JoinColumn({ name: 'campgroundId' })
    @Field(() => Campground)
    campground: Campground;

    @DeleteDateColumn()
    deletedAt: Date;
}

위와 같이 member와 campground의 id를 복합키로하여 Dibs라는 테이블을 생성하려고 합니다. 그런데 아래와 같은 오류가 발생했습니다.

[Nest] 21844 - 2023. 05. 24. 오전 9:36:44 ERROR [ExceptionHandler] Data type "Member" in "Dibs.member" is not supported by "mysql" database.

DataTypeNotSupportedError: Data type "Member" in "Dibs.member" is not supported by "mysql" database.

그런데 해당 코드를 아래와 같이 고치면 또 정상작동하게 됩니다.

@Entity()
@ObjectType()
export class Dibs {
    @ManyToOne(() => Member)
    @PrimaryColumn()
    @JoinColumn({ name: 'memberId' })
    @Field(() => Member)
    memberId: Member;

    @ManyToOne(() => Campground)
    @PrimaryColumn()
    @JoinColumn({ name: 'campgroundId' })
    @Field(() => Campground)
    campgroundId: Campground;

    @DeleteDateColumn()
    deletedAt: Date;
}

member -> memberId, campground -> campgroundId 라고 변수명만 바꿨을 뿐인데 정상작동하는 이유를 모르겠네요. member와 campground의 기본키가 각각 memberId와 campgroundId로 설정되어 있긴 합니다.

 

최종적으로 아래와 같이 구현했습니다.

@Entity()
@ObjectType()
export class Dibs {
    @ManyToOne(() => Member)
    @PrimaryColumn()
    @JoinColumn({ name: 'memberId' })
    @Field(() => String)
    memberId: string;

    @ManyToOne(() => Campground)
    @PrimaryColumn()
    @JoinColumn({ name: 'campgroundId' })
    @Field(() => String)
    campgroundId: string;

    @DeleteDateColumn()
    deletedAt: Date;
}

위 코드도 문제없이 잘 작동합니다.

 

그런데 애초에 복합키를 설정하는 것이 안 좋은 것일까요? 외래키 두개로 각 레코드들이 고유하게 구분될 수 있기 때문에 따로 기본키를 설정하지 않았는데 기본키를 설정하는게 더 좋은 방법일까요?

답변 1

1

노원두님의 프로필 이미지
노원두
지식공유자

안녕하세요! Haewoong님!

우선 현재 발생되고 있는 문제는 복합키의 문제는 아니에요!
따라서, 복합키와 관계없이 설명을 진행해 볼게요!

먼저, 아래와 같은 코드가 있다고 가정해 볼까요?!

  @PrimaryColumn()
  @Field(() => String)
  qqq: string;

위 코드에서 @PrimaryColumn()에 옵션을 설정하지 않는 경우, 디폴트로 @PrimaryColumn({ name: "qqq" })를 의미한답니다.
그리고, qqq라는 컬럼을 생성하려고 시도해요!


다음으로, 이번에는 조인과 관련된 코드가 있다고 가정해 볼까요?!

  @ManyToOne(() => ProductCategory)
  @JoinColumn()
  @Field(() => ProductCategory)
  productCategory: ProductCategory;

위 코드에서 @JoinColumn()에 옵션을 설정하지 않는 경우, 디폴트로 @JoinColumn({ name: "productCategoryId" })를 의미한답니다.
그리고 productCategoryId라는 컬럼을 생성하려고 시도해요!
실제로, @JoinColumn({ name: "qqqqqqqqqqqq" }) 등과 같이 입력하시게 되면, 데이터베이스 테이블에 qqqqqqqqqqqq 컬럼이 만들어져 있는 것을 확인하실 수 있으세요!

 

위 내용들을 바탕으로 정리하면, JoinColumn을 PrimaryKey로도 사용하고 싶은 경우 주의사항이 있어요!

  @ManyToOne(() => ProductCategory)
  @PrimaryColumn() // { name: "productCategory" }로 만들어 줘!
  @JoinColumn() // { name: "productCategoryId" }로 만들어 줘!
  @Field(() => ProductCategory)
  productCategory: ProductCategory;

위에서 @PrimaryColumn()과 @JoinColumn()에서 만들고자 하는 컬럼명이 서로 다르기 때문에 문제가 발생하고 있어요!

 

따라서, 해결 방법은 아래와 같습니다.

  @ManyToOne(() => ProductCategory)
  @PrimaryColumn({ name: "productCategoryId" })
  @JoinColumn({ name: "productCategoryId" })
  @Field(() => ProductCategory)
  productCategory: ProductCategory;

위 두 부분의 name을 모두 명시해 주시면 해결됩니다!^^

 

  • 추가 질문으로 복합키에 관하여 답변을 드리면, 복합키는 기존에 많이 사용되었지만 유연성이 떨어지는 관계로 점점 선호하지 않는 방향이 되어가고 있는 것 같아요!
    또한, 복합키의 사용시 주의 사항으로는 어쨌든 PK 이기 때문에, 한 번 만들어 지면 변경이 되지 않는 것이 좋습니다.
    PK가 변경된다는 것은 전체적인 구조상으로 여러가지 문제 상황을 발생시킬 수 있기 때문이에요.
    보여주신 예제에서는 Join되는 Key가 바뀌면 PK가 바뀌는 모양이 만들어지기 때문에 규모가 커지면 예상하지 못한 문제들을 발생시킬 수도 있습니다.

Haewoong Lee님의 프로필 이미지
Haewoong Lee
질문자

명쾌한 답변 감사합니다.

Haewoong Lee님의 프로필 이미지
Haewoong Lee
질문자

추가적으로 외래키를 지정할 때 클래스 자체를 자료형으로 해서 지정하는 방법과 해당 테이블의 id만 string으로 따와서 외래키를 지정하는 방법 중 어떤 것이 올바른 방법인가요? 상황에 따라 달라지는 것일까요? 그동안 클래스 자체를 외래키로 지정하는 방식을 사용했는데 이 방식은 조회할 때 해당 테이블의 다른 정보들도 한꺼번에 조회가 가능하지만 그만큼 요청 한번에 작업량이 많아져 성능에 문제가 생길 수도 있을 것 같아서요.

노원두님의 프로필 이미지
노원두
지식공유자

네! Haewoong님!

앞서 string과 class타입에 관한 비슷한 질문에 대해서 답변을 드렸었는데 첨언을 더 드리자면,

해당 컬럼이 조인이 될지 안될지, 조인컬럼으로 인식할지는 @ManyToOne() 데코레이터가 달려있는가 달려지있지 않은가가 중요합니다!

따라서, id가 string타입이어도, id가 class 타입이더라도 모두 연결된 데이터를 relations 옵션으로 조회하는 것이 가능해요!
문제는 조인id가 string 타입인 경우에는 조인하여 조회한 결과가 string이 아닌, 객체로 들어오게 될텐데 여기서 타입이 불일치되는 문제가 발생하게 됩니다.

  • 추가적으로, 성능 부분을 고민하고 계신 것 같은데 정말 좋은 자세인 것 같아요!
    답변 드리자면, id가 string 타입이든, class 타입이든 상관 없이 원하는 컬럼만 select 해오실 수 있으므로 해당 부분은 걱정하지 않으셔도 됩니다!^^
    select 옵션을 찾아보시거나, 이 부분이 부족한 경우에는 이후에 queryBuilder라는 기능을 사용하시는 것이 가능하세요!

 

Haewoong Lee님의 프로필 이미지
Haewoong Lee
질문자

아하.

그렇다면 외래키를 string으로 지정하면 결국에 타입 불일치 문제로 relations은 쓸 수 없기 때문에 연관 테이블 데이터는 한번에 조회하기가 불가능해지는 거네요??

노원두님의 프로필 이미지
노원두
지식공유자

@ManyToOne()으로 설정하시게 되면 타입이 string이던, class이던 상관 없이 relations으로 관련된 테이블 조회가 가능해요!
다만 조회해오고 난 결과 타입이 class가 아니라 string이기 때문에, 빨간줄이 뜨실거에요!

// @ts-ignore 이라는 키워드를 사용하시어 타입스크립트를 무시하시고 사용하실 수는 있으나, 굳이 추천드리지는 않아요!^^