Spring

도메인 분석 설계

date
Jun 28, 2023
slug
스프링부트-JPA-활용1-2
status
Public
tags
실전! 스프링 부트와 JPA 활용 1
author
summary
[실전! 스프링 부트와 JPA 활용 1] 섹션 2 강의 정리
type
Post
thumbnail
updatedAt
Jun 28, 2023 03:26 PM
category
Spring
김영한
인프런

📝 강의 정리


요구사항


[1]. 회원 기능

  • 회원 등록
  • 회원 조회

[2]. 상품 기능

  • 상품 등록
  • 상품 수정
  • 상품 조회

[3]. 주문 기능

  • 상품 주문
  • 주문 내역 조회
  • 주문 취소

[4]. 기타 요구사항

  • 상품은 재고 관리가 필요하다.
  • 상품의 종류는 [도서, 음반, 영화]가 있다.
  • 상품을 카테고리로 구분할 수 있다.
  • 상품 주문 시 배송 정보를 입력할 수 있다.
 

엔티티 설계 시 주의점


[1]. 양방향 연관관계를 지양하라

  • 도메인 설계 단에서 가급적 양방향 연관 관계가 아닌 단방향 관계로 설계해라.

[2]. 연관관계의 주인은 외래 키가 있는 곳으로 한다

  • 비즈니스상 우위로 주인을 정하지 않는다.
    • 자동차와 바퀴를 생각했을 때, 자동차를 연관관계 주인으로 설정하면 자동차가 관리하지 않는 바퀴 테이블의 FK 값이 업데이트 됨 → 관리 및 유지보수가 어렵고, 성능도 저하된다.

[3]. 엔티티 클래스의 @Setter는 꼭 필요한 경우에만 사용하라

  • @Setter를 열어둘 경우, 엔티티의 변경사항을 추적하기가 어렵다.
    • → 변경 지점이 명확하도록 비즈니스 메서드를 별도로 제공
  • 값 타입은 변경 불가능하게 설계해야 한다.
    • @Setter를 제거하고, 생성자에서 값을 모두 초기화하여 변경 불가능한 클래스로 만들기
      • @Embeddable @Getter public class Test { private String a; private String b; // JPA 스펙 상 기본 생성자는 public or protected로 설정해야 한다. // protected로 두는 것이 그나마 더 안전하다. protected Test() {} public Test(String a, String b) { this.a = a; this.b = b; } }

[4]. 상속 관계 전략은 부모 테이블에 지정해준다

  • @Inheritance(strategy = 테이블 전략)
    • InheritanceType.SINGLE_TABLE : 단일 테이블 전략
    • InheritanceType.JOINED : ERD 설계 관점 ( 정규화된 스타일 )
    • InheritanceType.TABLE_PER_CLASS

[5]. Enum 타입 클래스

  • Enum 타입의 필드 변수에는 @Enumerated(EnumType.[옵션]) 어노테이션을 달아준다.
    • EnumType.ORDINAL : 숫자 형식으로 들어가는데, 중간에 다른 상태가 추가되면 매핑 숫자가 밀리므로 사용 X
    • EnumType.STRING

[6]. 일대일 관계에서 연관관계의 주인은 조회가 많이 일어나는 곳

  • 일대일 관계에서는 FK를 어디다 두어도 상관은 없지만, 조회가 많이 일어나는 곳에 주로 둔다.

[7]. ❗️모든 연관관계는 지연로딩으로 설정

  • 즉시로딩( EAGER )는 예측이 어렵고, 어떤 SQL이 실행될 지 추적하기 어렵다.
    • → 특히 N + 1 문제가 자주 발생한다.
  • 실무에서 모든 연관관계는 지연로딩( LAZY )로 설정
  • @XToOne 관계는 기본이 즉시로딩이므로, 직접 지연로딩 설정 해줘야 함.

[8]. 컬렉션은 필드에서 초기화 하자

  • null 문제에서 안전하다.
  • hibernate는 엔티티를 영속화 할 때, 컬렉션을 감싸서 hibernate가 제공하는 내장 컬렉션으로 변경한다. 만약, getOrders() 와 같이, 임의의 메서드에서 컬렉션을 잘못 생성하면 hibernate 내부 메커니즘에 문제가 발생할 수 있다.
    • → 필드레벨에서 생성하는 것이 가장 안전하고, 코드도 간결하다.
      Member member = new Member(); System.out.println(member.getOrders().getClass()); em.persist(team); System.out.println(member.getOrders().getClass()); // class java.util.ArrayList // class org.hibernate.collection.internal.PersistentBag
 

[9]. cascade 옵션

@OneToMany(mappedBy = "order") // @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) private List<OrderItem> orderItems = new ArrayList<>(); /* @OneToMany(mappedBy = "order") persist(orderItemA); persist(orderItemB); persist(orderItemC); persist(order); => OrderItem 각각을 다 persist 해줘야 한다. @OneToMany(mappedBy = "order", cacade = CascadeType.ALL) persist(order); => order만 persist해주면 알아서 처리해준다. */

[10]. 연관관계 메서드 ( 연관관계 편의 메서드 )

  • JPA에서 엔티티 간 양방향 연관 관계를 설정했을 때, PK 쪽 엔티티의 필드는 mappedby 를 설정해서 읽기 전용으로 사용된다.
    • 여기서, N 쪽의 엔티티에만 연관 관계를 설정하면 객체 지향적이지 않을 뿐더러, DB에서 데이터를 조회하기 전까지는 N 쪽에만 값이 세팅되어 1쪽의 엔티티에서는 연관 관계를 알 수 없게 된다.
      이를 방지하기 위한 메서드를 연관관계 메서드 라고 한다.
      Member : Order = 1 : N // 연관관계의 주인이 되는 Order엔티티 // Order.java @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; // 연관관계 메서드 public void setMember(Member member){ // Order쪽 Member 값 설정 this.member = member; // Member쪽 Order 값 설정 member.getOrders().add(this); }
 
  • 처음엔 이해가 잘 되질 않았다. 양방향 관계의 경우 연관관계 메서드를 설정해주지 않아도, DB상에서 필터링된 데이터를 조회할 수 있다고 생각했다. 아래 댓글을 보고 이해할 수 있었다.
notion image

📎 출처