[SPRING] '배달 어플 프로젝트' 트러블슈팅 & 회고 - softDelete, 연관관계

림민지·2025년 4월 29일

Today I Learn

목록 보기
51/62

같이 보면 좋은 글
🔗 JPQL과 n+1 문제
🧩 프로젝트 와이어프레임 링크
🐚 프로젝트 깃허브 주소

목차
1. softDelete + 쿼리문 활용
2. entity 연관관계
3. postman 환경변수 설정
4. 회고


1️⃣ softDelete (feat. 쿼리문)

1. softDelete를 도입한 이유

Review, Store 같은 주요 엔티티에 소프트 삭제 기능을 도입했다.
기존에는 삭제 요청이 들어오면 DB에서 데이터를 완전히 삭제(하드 딜리트) 했는데, 이렇게 하면 나중에 복구하거나 기록을 추적할 수가 없었다...ㅜㅜ

그래서 방향을 바꿨다.

삭제 요청이 오면 DB에서 지우지 않고
deleted = true로 상태만 바꾸도록 했다.

즉, 눈에만 안 보이게 만들고 데이터는 살려두는 방식.
이러면 복구도 가능하고, 추후 로그로 활용하거나 감사용으로도 쓸 수 있다!

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
    ...생략
    public void softDelete() {
        this.deleted = true;
        this.deletedAt = LocalDateTime.now();
    }

    public boolean isDeleted() {
        return deleted;
    }
}

2. 문제 상황 : 삭제했는데 계속 보인다,,

기능은 넣었는데… 문제가 생겼다.
삭제 표시된 데이터도 API에서 계속 보이는 것,,,,

3. 왜 이런 일이 생겼을까?

deleted 플래그는 분명히 추가했지만, 조회할 때 그걸 걸러주는 조건을 안 넣었던 게 문제였다.

JPA는 기본적으로 모든 데이터를 다 가져오니까,
deleted = true인 데이터도 아무 조건 없이 다 포함돼서 내려왔다.

예를 들어 findAll()이findById()를 호출해도 삭제된 데이터가 쓱— 같이 따라오는 상황.....👻

4. 해결법: 자동으로 필터 걸기!💡

JPA에게 이렇게 말해보자
"이 테이블에서 가져올 땐, deleted = false 조건을 항상 붙여줘!"

이걸 도와주는 게 바로 ✨ @Where 어노테이션!!!!

소프트딜리트를 구현할 엔티티에 아래의 어노테이션을 달아 구현하면 된다!

@Where(clause = "deleted = false")
public class Menu extends BaseEntity {
   ...
}
항목결과
API 조회🔥 삭제된 데이터가 더 이상 내려오지 않음
프론트 UX👀 삭제된 가게/리뷰가 사용자 화면에 표시되지 않음
코드 수정 범위@Where 한 줄 추가로 간단하게 해결

5. 하드 삭제 vs 소프트 삭제, 어떤 기준으로 나눌까?🤔

구분하드 삭제소프트 삭제
방식DB에서 완전히 삭제삭제 여부만 표시
복구 가능성❌ 없음✅ 있음
장점저장 공간 절약, 단순데이터 추적 가능, 감사 로그에 유리
단점기록이 남지 않음조회 시 필터링 필요

중요한 사용자 데이터나 기록 또는 감사가 필요한 데이터는 소프트 삭제로,
테스트 데이터나 로그성 데이터는 하드 삭제로 처리하는 게 적당하다!


2️⃣ 연관 관계

🧠 연관관계 개념 정리

: 객체 지향적으로 데이터의 관계를 표현하기 위한 필수적 요소

1. OneToOne (1:1)
한 엔티티가 다른 하나의 엔티티와만 연결되는 관계
→ 한 사람은 하나의 여권만 가진다.

2. OneToMany (1:N)
한 엔티티가 여러 엔티티를 가질 수 있는 관계
→ 한 가게는 여러 메뉴를 가진다.

3. ManyToOne (N:1)
여러 엔티티가 하나의 엔티티에 속하는 관계
→ 여러 메뉴가 한 가게에 속한다. (OneToMany의 반대 방향)

4. ManyToMany (N:N)
여러 엔티티가 서로 여러 엔티티와 연결되는 관계
→ 한 학생은 여러 수업을 듣고, 한 수업에 여러 학생이 있다

🔥 문제 상황

처음에 이 관계를 설계할 때 진짜 너무 헷갈렸다...

StoreMenu는 어떤 관계지?
장바구니(Cart)는 사용자(User)랑 어떻게 연결하지?
무조건 @OneToMany만 쓰면 되는 거 아니야? (아님)

그래서 예시로 생각해봤다

🍔 햄버거 가게 예시

가게Store가 여러 개의 메뉴Menu를 팔잖아?
버거킹(가게) → 와퍼, 콰트로치즈, 감자튀김 등등

🔗 StoreMenu ➡️ @OneToMany !
: 가게 입장에서 메뉴가 여러 개니까
(하나의 가게는 많은 메뉴를 가진다)

🔗 MenuStore ➡️ @ManyToOne!!
(여러 메뉴가 하나의 가게에 속함)
: 메뉴 하나는 어디 가게 메뉴인지 알아야 하니까

그럼 메뉴 입장에서는 가게가 하나니까
🔗 MenuStore ➡️ @ManyToOne
:메뉴 입장에서는 가게가 하나니까
(여러 메뉴가 하나의 가게에 속함)

🔗 UserCart ➡️ @OneToMany !
: OneToOne도 맞지만, 향후 유저가 여러개의 장바구니를 가질 수 있도록 리팩토링될 가능성이 있기때문에 원투매니로 설정하는게 좋다!
(유저는 각각 고유의 장바구니를 가진다)

🔗 CartCartItem ➡️ @OneToMany !
: 장바구니는 여러개의 아이템을 가질 수 있다

🔗 CartStore ➡️ @ManyToOne !
: 하나의 가게에 A유저가 장바구니를 생성하고, B유저도 장바구니를 생성할 수 있기때문에
(유저는 해당 가게 이외의 가게에서 품목을 담을 시 초기화된다)


✒️ 회고

✅ Keep (잘한 점 - 계속 유지하고 싶은 것)

  • 작은 단위별로 마감 기한을 정한 것
  • Github Issue & PR & Project 기능 활용
  • 성공, 실패 API 응답 통일

💪 Try (다음 프로젝트에서 시도해보고 싶은 것)

  • Redis 사용해보기
    → 검색어 필터링하기, refresh 토큰 저장 등등..

  • 프론트와 연결하는 방법 공부해보기
    → Figma + Al 로 프론트 생성해보기 https://theinnovators.zone/archives/4185

  • 외부 API 연동

  • OAuth 구현해보기

⚠️ Problem (아쉬웠던 점- 개선이 필요한 부분)

  • 프로젝트 진행 중 어려웠던 점을 다 같이 튜터님 찾아뵙고 피드백을 받지 못했던 것
  • 통합테스트 못해봐서 아쉬웠습니다..
  • 5분 기록보드를 활용하지 못한 것
  • 통합 테스트를 못해본 것
  • 작업한 것들을 어딘가에 기록해놓지 못한 것ㅠㅠ😭
    → 다음에는 단위, 통합 테스트 모두 진행하고 싶고, 기록도 조금씩 남겨보
profile
@lim_128

0개의 댓글