도메인 분석 설계

이가현·2022년 10월 6일
0

도메인 모델과 테이블 설계


@OneToMany
@ManyToOne
@OneToOne

엔티티 클래스 개발

  • Setter를 호출하면 데이터가 변하기 때문에 변경 사항을 추적하기 힘들어진다.
    ➡️ 실무에서는 가급적 Getter는 열어두고, Setter는 꼭 필요한 경우에만 사용하는 것을 추천!
  • 엔티티를 변경할 때는 Setter 대신에 변경 지점이 명확하도록 변경을 위한 비즈니스 메서드를 별도로 제공해야 한다.
    ➡️ 엔티티나 임베디드 타입은 기본생성자를 public보다는 protected로 설정하는게 안전합니다.

*임베디드 타입은 @Embeded@Embeddable으로 표현


💡 상속관계 매핑

  • 1️⃣ 각각의 테이블로 변환
    @Inheritance(stragy = InheritanceType.JOINED)

    ✅ 자식 테이블들을 DTYPE으로 구분하여 사용할 수 있음.
    ✅ 상속받는 자식 테이블을 각각 만들고, 부모 테이블의 PK를 자식 테이블의 PK이자 FK로 사용함.
    ➡️ 이를 통해 다른 테이블에서 부모 테이블만 보도록 설계할 수 있음.

  • 2️⃣ 단일 테이블로 변환
    @Inheritance(stragy = InheritanceType.SINGLE_TABLE)

    자식 테이블들을 통합한 단일 테이블로 사용할 수 있음.
    @DiscriminatorValue("...") = 각각의 테이블들이 하나의 싱글테이블로 합쳐지기에 DB에서 테이블을 식별할 수 있도록 구분할 수 있는 값을 넣어줘야 함.

  • 3️⃣ 서브타입 테이블로 변환
    @Inheritance(stragy = InheritanceType.TABLE_PER_CLASS)

    자식 엔티티들을 각각의 테이블로 만들어 사용함.
    ✅ 여러 자식들을 조회할 때, UNION을 사용하여 성능이 저하되며 다른 여러 단점들이 존재하여 자주 사용 X


💡 즉시로딩 vs 지연로딩

모든 연관관계에서 FetchType을 🔴 지연로딩(LAZY) 🔴 으로 설정해야 한다 !!

  • 즉시로딩 (FetchType - EAGER) : 엔티티를 조회할 때 연관된 엔티티를 즉시 조인해서 값을 넣어주고, 함께 조회함.
    단점 : 예상하지 못한 SQL이 발생하고, 굉장히 많은 문제를 발생시킬 가능성 존재

  • 지연로딩 (FetchType - LAZY) : 엔티티를 조회할 때 연관된 엔티티를 조인하지 않음. 즉 값을 넣어주지 않는다.
    대신, 연관된 엔티티를 참조할 때 조인 쿼리를 작성하여 값을 넣어준다.
    (필요할 때 넣어준다는 뜻 !)
    ➡️ 해당 객체를 조회할 때 그 객체를 조회하는 쿼리가 발생하게 된다.

➡️ @...ToOne 의 경우, FetchType의 디폴트가 즉시로딩으로 되어있으므로
어떤 SQL이 실행될지 추적하기 어렵기 때문에 컬렉션은 null문제 해결을 보장하기위해 필드에서 초기화하는 것이 안전함.
(@OneTo...의 디폴트는 LAZY이기에 따로 설정안해도 됨.)


💡 JPA 연관관계 매핑

데이터베이스에서 무조건 다(N)쪽이 외래 키(FK)를 갖는다 !!

  • 1️⃣ 다대일 (N:1)
    다(N)쪽인 엔티티에 @ManyToOne을 추가해주고, @JoinColumn("반대 쪽_id")을 추가해 반대 쪽(1)의 외래키를 갖음.
	@ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    private Category parent;
  • 2️⃣ 일대다 (1:N)
    일대다(단방향)는 다대일의 반대 입장이므로 같은 것이라고 생각할 수 있지만
    이는 다(N)쪽이 아닌 일(1)쪽에 연관관계의 주인을 두는 방식 !
    이는 일(1)만 수정했을 때, 다(N)쪽에서도 수정이 생겨 쿼리에 문제가 생기기 때문에
    다대일(N:1) 양방향 연관 관계를 매핑하는게 추후에 유지보수에도 수월함.
 	@OneToMany(mappedBy = "parent")
    private List<Category> child = new ArrayList<>();
  • 3️⃣ 다대다 (N:M)
    두 테이블 사이에 양쪽의 PK를 FK로 갖는 연결 테이블을 만들어 사용함.
    즉, 중간 엔티티를 만들고 @ManyToOne , @OneToMany 로 매핑해서 사용하자.

💡 영속성 전이 (JPA cascade)

CascadeType의 종류로는 ALL, PERSIST, REMOVE, MERGE, REFRESH, DETACH가 있으나 자주 사용하는 건 세 개 정도.

  • ALL : 모두 적용 (PERSIST + REMOVE + ...)
  • PERSIST : 영속
  • REMOVE : 삭제

이는 엔티티를 영속화할 때 연관된 엔티티들도 함께 영속화해준다는 점에서 편리함을 제공하는 것이지,연관관계를 매핑하는 것과는 상관 ✖︎

ex)

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
 private List<OrderItem> orderItems = new ArrayList<>();

엔티티 설계시 주의점

💡 컬렉션은 필드에서 초기화 !

컬렉션은 필드에서 바로 초기화 하는 것이 안전하다. (선언과 초기화를 같이 하자 !)

  • null 문제에서 안전하다.
  • 하이버네이트는 엔티티를 영속화 할 때 컬랙션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경하기 때문에, 만약 getOrders() 처럼 임의의 메서드에서 컬력션을 잘못 생성하면 하이버네이트 내부 메커니즘에 문제가 발생할 수 있다.

➡️ 따라서 필드레벨에서 생성하는 것이 가장 안전하고, 코드도 간결하다.

ex)

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();

💡 테이블, 컬럼명 생성 전략

스프링 부트에서 하이버네이트 기본 매핑 전략을 변경해서 실제 테이블 필드명은 다름 !

스프링 부트 신규 설정 (엔티티(필드) ➡️ 테이블(컬럼))

    1. 카멜 케이스 ➡️ 언더스코어(memberPoint ➡️ member_point)
    1. .(점) ➡️ _(언더스코어)
    1. 대문자 ➡️ 소문자

💡 Enum 타입 지정 시 주의할 점

@Enumerated(EnumType.STRING)
private DeliveryStatus status; //ENUM [READY(준비), COMP(배송)]

새로운 게 추가 되었을 때를 고려해줘야 하기 때문에,
EnumType은 꼭 스트링으로 해주기 !!

0개의 댓글