스프링 부트와 JPA 활용1 - 2. 도메인 분석 설계

Updated:

김영한님의 인프런 강의 - 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
위 강의를 정리한 내용입니다.

I. 도메인 모델과 테이블 설계

  • 테이블간 다대다 관계는 지양하자.
  • 회원이 주문리스트를 갖는 것은 보기엔 그럴듯하나 좋지 않다. 회원이 주문을 참조하는 것이 아니라 주문이 회원을 참조하는 것이 바람직하다.
  • 다대다 관계는 관계형 데이터베이스에서는 다대일 + 일대다 관계로 매핑하여 구현해야 한다.
  • 일대다 관계에서는 무조건 ‘다’쪽에 외래키가 존재한다.
  • 외래 키가 있는 곳을 연관관계의 주인으로 정해라.

II. 엔티티 클래스 개발

어노테이션

@Column / @Table

@Column(name = “member_id”)
@Table(name = “orders”)

  • 컬럼명 및 테이블명을 직접 바꿀 수 있다.

@Embeddable, @Embedded

  • Entity안의 값들을 더 의미있는 값으로 응집시켜 하나의 객체로 표현하고, 결과적으로 가독성을 높일 수 있다.
  • 해당 클래스에는 @Embeddable을, 필드로 선언하는 클래스의 선언부에는 @Embedded를 붙여주자(하나만 붙여도 작동한다).
// Address.java
@Embeddable
public class Address {
}

// Member.java
@Embedded
private Address address;

@ManyToOne / @OneToMany / @OneToOne

  • 각각 다대일, 일대다, 일대일에 해당하는 필드에 붙여준다.
  • 매핑되는 쪽에 무엇에 의해 매핑되어지고 있는지 표기

    @OneToMany(mappedBy = “member”)

@JoinColumn(name = “member_id”)

  • 해당 필드를 name에 해당하는 외래키에 매핑한다.

@Inheritance

  • 관계형 데이터베이스에는 상속 관계가 없고, 슈퍼타입 서브타입이라는 유사관계만 존재한다.
  • 객체의 상속관계와 DB의 슈퍼타입 서브타입 관계를 매핑하는 것을 상속관계 매핑이라 한다.
  • InheritanceType에는 3가지 종류가 있다.
    • JOINED
    • SINGLE_TABLE
    • TABLE_PER_CLASS

InheritanceTYPE.JOINED

  • ITEM 테이블에는 공통정보가, 개별 정보들은 ALBUM, MOVIE, BOOK 테이블에 각각 저장된다.
  • 하이버네이트의 조인 전략에서는 @DiscriminatorColumn을 추가로 선언해야 DTYPE컬럼이 생성된다.
  • 테이블이 정규화 되어있다.
  • 총 4개의 테이블이 생성된다.

InheritanceTYPE.SINGLE_TABLE

  • 서비스 규모가 크지않고 간단히 구현하고 싶을 때 사용한다.
  • 한 테이블에 다 저장하고 DTYPE으로 구분한다.
  • INSERT, SELECT쿼리가 한 번에 이루어지고 조인이 필요없기에 성능이 좋다.
  • 1개의 테이블만 생성된다.

InheritanceTYPE.TABLE_PER_CLASS

  • JOIN전략에서 슈퍼 타입이 가지고 있던 컬럼들을 서브 타입에 모두 나눠준다.
  • ITEM 엔티티는 실제 생성되지 않으므로 abstract클래스여야 한다.
  • 총 3개의 테이블이 생성된다.

@Enumerated

  • EnumType.ORDINAL : enum 순서 값을 DB에 저장한다.
  • EnumType.STRING : enum 이름을 DB에 저장한다.

ORDINAL의 경우 enum 타입이 새로 추가되면 순서가 뒤바뀌어
심각한 오류를 일으킬 수 있다. 기본 셋팅이 ORDINAL이므로 꼭 enum타입을 사용할 때에는 STRING 을 사용하자.

참고 - Getter, Setter

  • 실무에서는 가급적 Getter는 열고, Setter는 정말 필요할 때만 사용해야 한다.
    • Getter는 단순히 조회하는 일만 하므로 상관없지만, Setter는 호출 시 데이터가 변경될 수 있기 때문에 추후 문제 발생시 원인추적이 어렵다.
    • Setter는 로직에 따라 별도의 메서드를 제공해야 한다.

III. 엔티티 설계시 주의점

  • 엔티티에는 가급적 Setter를 사용하지 말자
    • 변경 포인트가 너무 많아서 유지보수가 어렵다.
  • 모든 연관관계는 지연로딩으로 설정
    • 즉시로딩으로 설정 시 N+1문제 자주 발생
    • @*ToOne 관계는 모두 기본 설정이 지연로딩이므로 (fetch = FetchType.LAZY)를 붙여주자
  • 컬렉션은 필드에서 바로 초기화 하자
  • cascade = CascadeType.ALL옵션을 통해 일괄 persist
  • 양방향 관계인 경우 연관관계 편의 메서드를 사용
      //==연관관계 메서드==//
      public void setMember(Member member) {
          this.member = member;
          member.getOrders().add(this);
      }
        
      public void addOrderItem(OrderItem orderItem) {
          orderItems.add(orderItem);
          orderItem.setOrder(this);
      }
        
      public void setDelivery(Delivery delivery) {
          this.delivery = delivery;
          delivery.setOrder(this);
      }
    
    • 왜 Order엔티티에만 적용했는가?
      • 남발하면 엔티티를 저장할때 어떤 연관 엔티티들이 함께 저장되는지를 계속 추적해야 한다. 통상적으로 cascade는 완전히 개인 소유하고 있는 엔티티일때 사용하는 것이 좋다. 예제에서는 Order가 OrderItem을 개인소유하기 때문에 cascade를 사용했지만, 사실 Delivery는 조금 애매하다.

Leave a comment