[스프링부트와 JPA활용1] - 엔티티 클래스 개발

박준수·2022년 11월 15일
0


  • 주문과 상품은 다대다 관계다. 하지만 이런 다대다 관계는 데이터베이스는 물론이고 엔티티에서도 거의 사용하지 않는다. 따라서 주문상품이라는 엔티티를 추가해서 다대다 관계를 이대다, 다대일 관계로 풀어냈다.
  • 상품은 도서, 음반, 영화로 구분되는데 상품이라는 공통 속성을 사용하므로 상속 구조로 표현했다.

참고: 외래 키가 있는 곳을 연관관계의 주인으로 정해라.
연관관계의 주인은 단순히 외래 키를 누가 관리하냐의 문제이지 비즈니스상 우위에 있다고 주인으로 정하면
안된다.. 예를 들어서 자동차와 바퀴가 있으면, 일대다 관계에서 항상 다쪽에 외래 키가 있으므로 외래 키가 있는 바퀴를 연관관계의 주인으로 정하면 된다. 물론 자동차를 연관관계의 주인으로 정하는 것이 불가능 한 것은 아니지만, 자동차를 연관관계의 주인으로 정하면 자동차가 관리하지 않는 바퀴 테이블의 외래 키 값이 업데이트 되므로 관리와 유지보수가 어렵고, 추가적으로 별도의 업데이트 쿼리가 발생하는 성능 문제도 있다.

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

@Column

객체 필드를 테이블의 컬럼에 매핑시켜주는 어노테이션입니다.
속성 name => 필드와 매핑할 테이블의 컬럼 이름을 지정한다.

@Embeddable와 @Embedded

회원과 배송에서 필드에 private String city; private String street; private String zipcode; 를 넣으면 주소로 인식하기에는 약간의 어려움이 있다. 필드가 더 많아진다면 더더욱 지저분하게 될것이다. 그래서 조금 더 읽기 쉬운 코드로 만들어 보자.

앞의 주소를 표현하는 3개의 데이터를 1개의 주소라는 의미의 객체로 표현한다면 훨씬 더 가독성이 좋은 코드로 만들 수 있다. JPA Entity안의 Column을 하나의 객체로써 사용하고 싶다면 @Embedded, @Embeddable어노테이션을 사용해야 한다.

@Embeddable

  • 주소: 값 타입(임베디드 타입이다).
  • 회원과 배송에서 사용한다.

@Embedded

@Enumerated(EnumType.String)

자바 enum타입을 엔티티 클래스의 속성으로 사용할 수 있다.

  • Enumtype.ORDINAL => enum 순서 값을 DB에 저장
  • Enumtype.STRING => enum 이름을 DB에 저장

ORDINAL 지정 했을때 : DeliveryStatus 속성에 DeliveryStatus.READY 값을 세팅하면 DB에 저장되는 값은 1이다. DeliveryStatus.COMP값으로 셋팅하면? 선언되어 있는 순서가 값이 되기 때문에 2가 DB에 저장된다.

STRING으로 지정했을때 : "READY", "COMP"문자열 자체가 저장된다.

@Inheritance(strategy =InheritanceType.SINGLE_TABLE), @DiscriminatorValue("A")

단일 테이블 전략 : 테이블을 하나만 사용하는 방식.

  • DTYPE과 같이 구분 컬럼으로 어떤 자식 데이터가 저장되어있는지를 구분합니다.
  • 이 전략은 조회할 때 조인을 사용하지 않기 때문에 일반적으로 가장 빠르다.
  • 자식 엔티티에 매핑된 컬럼은 모두 null을 허용해야 엔티티 별로 데이터를 저장할 수 있다.
  • @Inheritance(strategy =InheritanceType.SINGLE_TABLE) ==> 단일 테이블 전략 사용
  • @DiscriminatorValue() ==> 자식 구분

참고 : 상속관계매핑

@OneToMany @ManyToOne 양방향 매핑

양방향 매핑 규칙

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정한다.
  • 연관관계의 주인만이 외래 키를 관리(등록, 수정)한다. DB에 접근한다.
    • 객체에 둘다 정보를 업데이트 해도, 연관관계의 주인 것만 DB에 영향을 준다.
  • 주인이 아닌 쪽은 읽기만 가능
  • mappedBy 키워드로 연관관계 매핑이 되었다. 라는 것을 의미한다.
  • 주인은 mappedBy 속성 사용하지 않고, @JoinColumn을 사용한다.
  • 주인이 아니면 mappedBy 속성으로 주인을 지정한다.

누구를 주인으로 해야할까?

  • 외래 키가 있는 곳을 주인으로 정해라!!!!
  • 멤버와 주문이 일대다 관계인데, DB에서 보면 다 쪽이 FK를 가지고 있다.


회원-주문 => 주문이 주 테이블

참고 : 양방향 연관관계, 연관관계의 주인

@OneToOne 양방향 매핑

일대일 관계는 특이하게 주 테이블이나 대상 테이블 중에 외래 키를 넣을 테이블을 선택 가능하다.

  • 주 테이블에 외래 키 저장
  • 대상 테이블에 외래 키 저장

보통 주 테이블에 외래 키 저장

  • 주 테이블 : 많이 접근하는 테이블
  • 주 객체(많이 사용하는 객체)가 대상 객체의 참조를 가지는 것 처럼
  • 객체지향 개발자들이 선호하고 , JPA 매핑이 편리하다.

배달-주문 => 주문이 주 테이블

참고 : @OneToOne 양방향 매핑

JPA 영속성 전이(CASCADE)

영속성전이(CASCADE)란..?

부모 엔티티가 영속화될 때 자식 엔티티도 같이 영속화되고, 부모 엔티티가 삭제될 때 자식 엔티티도 삭제된느 등 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 전이되는 것을 의미합니다.

즉, 특정 엔티티에 대해 특정한 작업을 수행하면 관련된 엔티티에도 동일한 작업을 수행한다는 의미입니다.

jpa에서는 연관된 엔티티간의 의존성을 설정하기 위해 Enum 타입의 javax.persistence.CascadeType 을 제공하고 있습니다.

  • CascadeType.ALL: 모든 Cascade를 적용
  • CascadeType.PERSIST: 엔티티를 영속화할 때, 연관된 엔티티도 함께 유지
  • CascadeType.MERGE: 엔티티 상태를 병합(Merge)할 때, 연관된 엔티티도 모두 병합
  • CascadeType.REMOVE: 엔티티를 제거할 때, 연관된 엔티티도 모두 제거
  • CascadeType.DETACH: 부모 엔티티를 detach() 수행하면, 연관 엔티티도 detach()상태가 되어 변경 사항 반영 X
  • CascadeType.REFRESH: 상위 엔티티를 새로고침(Refresh)할 때, 연관된 엔티티도 모두 새로고침

참고 : Cascade

JPA 지연로딩(Lazy), 즉시로딩(Eager)

즉시 로딩(Eager)

데이터를 조회할 때 연관된 데이터까지 한 번에 불러오는 것이다.
예를 들어) Member 엔티티와 Team 엔티티가 N:1 매핑으로 관계를 맺고 있다. 즉시 로딩(EAGER) 방식을 사용하면 Member를 조회하는 시점에 바로 Team까지 불러오는 쿼리를 날려 한번에 데이터를 불러온다.

지연 로딩(Lazy)

필요한 시점에 연관된 데이터를 불러오는 것. 지연 로딩으로 설정하고 Member를 조회해 보면 Team을 조회하는 쿼리가 생성되지 않고 Member를 조회하는 쿼리만 나가고, 실제로 팀을 사용하는 시점에 Team을 조회하는 쿼리가 나간다.

Member와 연관된 Team이 1개이면 즉시 로딩으로는 Team을 조회하는 쿼리가 1개 나가지만, 만약 Member를 조회하는 JPQL을 날렸는데 연관된 Team이 1000개라면 Member를 조회하는 쿼리를 하나 날렸을 뿐인데 Team을 조회하는 SQL 쿼리 1000개가 추가로 나가게 된다. 그렇기 때문에 가급적이면 기본적으로 지연 로딩을 사용하는 것이 좋다.

@XToOne(OneToOne, ManyToOne) 관계는 기본이 즉시로딩이므로 직접 지연로딩으로 설정해야 한다.

(주문엔티티)

참고 : 지연로딩을 써야하는 이유

profile
방구석개발자

0개의 댓글