[실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발] 02. 도메인 분석 설계

Turtle·2024년 7월 3일
0
post-thumbnail

🙄요구사항 분석

  • ✔️기능 목록
    • 회원 기능
      • 회원 등록
      • 회원 조회
    • 상품 기능
      • 상품 등록
      • 상품 수정
      • 상품 조회
    • 주문 기능
      • 상품 주문
      • 주문 내역 조회
      • 주문 취소
    • 기타 요구사항
      • 상품은 재고관리가 필요하다.
      • 상품의 종류는 도서, 음반, 영화가 있다.
      • 상품을 카테고리로 구분할 수 있다.
      • 상품 주문시 배송 정보를 입력할 수 있다.

🙄도메인 모델과 테이블 설계

✔️관계 정리

  • 회원은 여러 상품을 주문할 수 있다. → 회원과 상품은 일대다 관계
  • 한 번 주문할 때 상품을 여러 가지 선택할 수 있으면서 하나의 상품은 여러 주문 내역에 포함될 수 있다. → 주문과 상품의 관계는 다대다 관계
    • ❗하지만 실무에서는 다대다 관계를 사용하지 않고 이를 일대다, 다대일 관계로 풀어내야 한다.
    • ❗따라서 주문과 주문상품(주문 목록의 상품들), 그리고 주문상품(주문 목록의 상품들)과 상품의 관계로 분리한다.
    • ❗한 번 주문할 때 여러 주문상품을 넣을 수 있으므로 일대다 관계, 그리고 하나의 상품이 여러 주문상품에 들어갈 수 있으므로 다대일 관계가 된다.
  • 한 번 주문하면 한 번의 배송이 이루어진다. → 주문과 배송은 일대일 관계
  • 하나의 카테고리에 여러 상품이 포함될 수 있으면서 하나의 상품이 여러 카테고리에 포함될 수 있다. → 카테고리와 상품의 관계는 다대다 관계
  • 상품의 종류인 도서, 음반, 영화는 상품을 상속받는다.

✔️엔티티 정리

  • 회원(Member)에는 임베디드 타입인 주소, 그리고 주문 리스트를 가진다.
  • 주문(Order)에는 한 번 주문시 여러 상품을 주문할 수 있으므로 주문과 주문상품은 일대다, 주문은 상품을 주문한 회원과 배송 정보, 주문 날짜, 주문 상태를 가지고 있다. 주문 상태는 열거형을 사용했는데 주문(ORDER), 취소(CANCEL)로 표현할 수 있다.
  • 주문상품(OrderItem) : 주문한 상품 정보와 주문 금액(orderPrice), 주문수량(count) 정보를 가지고 있다.
  • 상품(Item) : 이름, 가격, 재고수량을 가지고 있다. 상품을 주문하면 재고수량이 줄어든다. 상품의 종류로는 도서, 음반, 영화가 있는데 각각 사용하는 속성은 조금씩 다르다.
  • 배송(Delivery) : 주문시 하나의 배송 정보를 생성한다.
  • 카테고리(Category) : 상품과 다대다 관계를 맺는다. Parent, Child로 부모, 자식 카테고리를 연결한다.
  • 주소(Address) : 값 타입이다. 회원과 배송에서만 사용한다.

✔️테이블 정리

  • 상품(Item) : 앨범, 도서, 영화 타입을 통합해서 하나의 테이블로 만들었다. DTYPE 컬럼으로 타입을 구분한다.

✔️연관관계 매핑 정리

  • 회원과 주문 : 다대일 양방향 관계
  • 주문상품과 주문 : 다대일 양방향 관계
  • 주문상품과 상품 : 다대일 단방향 관계
  • 주문과 배송 : 일대일 양방향 관계
  • 카테고리와 상품 : 다대다 관계(예제 수준의 내용임을 감안)

🙄엔티티 클래스 개발

  • 예제에서는 설명을 쉽게 하기 위해 엔티티 클래스에 Getter/Setter를 모두 열고 최대한 단순하게 설계
  • 하지만 실무에서는 가급적 Getter는 열어두고 Setter는 꼭 필요한 경우에만 사용하는 것을 추천

참고1: 이론적으로 Getter/Setter 모두 제공하지 않고, 꼭 필요한 별도의 메서드를 제공하는게 가장 이상적이다. 하지만 실무에서 엔티티의 데이터는 조회할 일이 너무 많으므로 Getter의 경우 모두 열어두는 것이 편리하다. Getter는 아무리 호출해도 호출하는 것만으로 어떤 일이 발생하지 않는다. 하지만 Setter는 문제가 다르다. Setter를 호출하면 데이터가 변한다. Setter를 막 열어두면 가까운 미래에 엔티티가 도대체 왜 변경되는지 추적하기 점점 힘들어진다. 그래서 엔티티를 변경할 때는 Setter 대신에 변경 지점이 명확하도록 변경을 위한 비즈니스 메서드를 별도로 제공해야 한다.

참고2: 실무에서는 @ManyToMany를 사용하지 말자. @ManyToMany는 편리한 것 같지만 중간 테이블에 컬럼을 추가할 수 없고, 세밀하게 쿼리를 실행하기 어렵기 때문에 실무에서 사용하기에는 한계가 있다. 중간 엔티티를 만들고 @ManyToOne, @OneToMany로 매핑해서 사용하도록 하자. 정리하면 다대다 매핑을 일대다 매핑, 다대일 매핑으로 풀어내서 사용하자.

참고3: 값 타입은 변경 불가능하게 설계해야 한다. @Setter를 제거하고 생성자에서 값을 모두 초기화해서 변경 불가능한 클래스를 만들자. JPA는 스펙상 엔티티나 임베디드 타입은 자바 기본 생성자를 public 또는 protected로 설정해야 한다. public으로 두는 것보다는 protected로 설정하는 것이 그나마 더 안전하다. JPA가 이런 제약을 두는 이유는 JPA 구현 라이브러리가 객체를 생성할 때 리플렉션 같은 기술을 사용할 수 있도록 지원해야 하기 때문이다.

🙄엔티티 설계시 주의점

주의점1: 엔티티에는 가급적 Setter를 사용하지 말자. 변경 포인트가 너무 많아서 유지보수가 어려워진다.

주의점2: 모든 연관관계는 지연로딩으로 설정하자. 즉시로딩은 예측이 어렵고 어떤 SQL이 실행될지 추적이 어렵다. 특히 JPQL을 실행할 때 N+1 문제가 자주 발생한다. 연관된 엔티티를 함께 DB에서 조회해야 하면 fetch join 또는 엔티티 그래프 기능을 사용한다. @XToOne(OneToOne, ManyToOne) 관계는 기본이 즉시로딩이므로 직접 지연로딩으로 설정해야 한다.

주의점3: 컬렉션은 필드에서 초기화하자. 컬렉션은 필드에서 초기화하는 것이 안전하다. null문제에서 안전하다. 하이버네이트는 엔티티를 영속화할 때, 컬렉션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경한다. 만약 getOrders()처럼 임의의 메서드에서 컬렉션을 잘못 생성하면 하이버네이트 내부 메커니즘에 의해 문제가 발생할 수 있다. 따라서 필드레벨에서 생성하는 것이 가장 안전하고 코드도 간결하다.

0개의 댓글