본문 바로가기
포트폴리오

TypeORM에서의 @DeletedDateColumn를 활용한 Soft Delete 관련 트러블슈팅

by Yura 🌼 2025. 2. 20.
728x90

문제 상황

이전에 생성한 엔티티를 바탕으로 Repository Pattern을 구현할 때 생겨난 문제입니다.

(Repository Pattern은 우리가 entity를 통해 만들어준 테이블의 값 들이 어떠한 일련의 과정을 통해 서비스에서 접근 가능하게끔 해야 하는데 그때 repository가 매개체로서 이 과정을 수행한다. 즉, repository에서 entity로 정의해 놓은 DB의 값을 받고, service에 주입시켜 준다고 생각하면 이해하기 편합니다.)

제가 생성한 엔티티는 다음과 같습니다.

//order.entity.ts 일부 
  @CreateDateColumn({ type: 'datetime' })
  created_at: Date;

  @UpdateDateColumn({ type: 'datetime' })
  updated_at: Date;

  @DeleteDateColumn({ type: 'boolean', default: false })
  deleted: boolean;
  ingredients: { name: string; imageUrl: string }[];

 //pizza.service.ts 
 async getPizzaByOrder(id: number): Promise<ResponsePizzaDto> {
    const order = await this.orderRepository.findOne({
      where: { id },
      relations: ['connects', 'connects.ingredient'],
    });
    if (!order) {
      throw new NotFoundException(`Can't find pizza with id ${id}`);
    }
    console.log(order.connects[0].ingredient);

    const response: ResponsePizzaDto = {
      pizzaId: order.id,
      pizzaName: order.name,
      pizzaScript: order.script,
      ingredients: {
        ingredientName: '',
      },
    };

위에서 만든 엔티티를 바탕으로 DB에 저장된 id값을 가진 피자이름을 조회해보려고 콘솔에 찍어보려고 하는데 문제의 ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'id')가 나왔습니다.

해결 과정

  1. 처음에는 커스텀해서 만든 레포지토리가 모듈에 장착이 안돼서 생기는 에러인 줄 알았습니다. (아님)
  2. 그리고 TypeORM 설정에서 logging: true 설정을 추가하여 어떤 쿼리가 실행되는지 로그에서 확인해보고자 하였습니다.


설정을 해주고 나서 if문에 걸리지않는걸 보아하니 orderRepository.findOne이 잘 주입된 것을 알았습니다. 그러나 조회 결과: null 이 나왔습니다 (분명히 DB안에 데이터들이 잘 들어있는데 말이죠…)

 

문제는 데이터 조회조건에 있었는데, TypeORM은 @DeletedDateColumn이 적용된 엔티티의 경우 소프트 삭제(Soft delete)를 위해 해당 칼럼이 null인 레코드만 조회하도록 자동으로 처리하고 있었습니다.

저의 경우, @DeletedDateColumn의 default값이 0으로 설정되어 있었기 때문에 조회결과가 null이었던 것입니다.

잠시 논리삭제(soft delete)와 물리삭제(hard delete)에 대해 알고가도록 해요

백엔드에서 삭제하는 방식은 크게 두가지가 있습니다.

  • 논리 삭제: 저장된 데이터를 사용하지 않아서 논리적으로만 삭제하는 방법
  • 물리 삭제: 저장된 데이터를 실제로 삭제해버리는 방법

논리 삭제를 사용하는 이유

  1. AI 학습 데이터로 이용
  2. 주고받은 채팅 내역일 때에, 법적인 문제가 생겼을 때 증거로 사용
  3. 관계(relation)가 맺어져 있을 때 문제가 생김

주로 물리 삭제는 위험하기 때문에 대부분의 서비스에 논리 삭제(soft delete)를 이용합니다.

저 또한 혹시 있을 인게임에서 레시피 업데이트 및 수정이 있을지도 모르기 때문에 소프트 삭제 기능을 사용하기로 했습니다.

그래서 결론은요

1. 소프트 삭제 기능을 사용할 경우

@DeletedDateColumn의 타입을 datetime(혹은 Date)로 지정하고 기본값을 제거

@DeleteDateColumn({ type: 'datetime', nullable: true })
deletedAt: Date;

2. 소프트 삭제 기능을 사용하지 않을 경우

삭제 여부를 Column으로 관리하지 않는다 !

 

해당 컬럼을 소프트 삭제 기능을 사용하고 워크벤치에서 반영이 잘되고 문제없이 잘 돌아가는 것을 알 수 있었습니다.

API 조회 결과

 

테이블 수정 이후

 

긴 글 읽어주셔서 감사합니다.

혹시라도 잘못 전달된 정보가 있을 경우에는 댓글 남겨주시면 감사하겠습니다. 😉

728x90