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]. 연관관계의 주인은 외래 키가 있는 곳으로 한다[3]. 엔티티 클래스의 @Setter는 꼭 필요한 경우에만 사용하라[4]. 상속 관계 전략은 부모 테이블에 지정해준다[5]. Enum 타입 클래스[6]. 일대일 관계에서 연관관계의 주인은 조회가 많이 일어나는 곳[7]. ❗️모든 연관관계는 지연로딩으로 설정[8]. 컬렉션은 필드에서 초기화 하자[9]. cascade 옵션[10]. 연관관계 메서드 ( 연관관계 편의 메서드 )📎 출처
📝 강의 정리
요구사항
[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상에서 필터링된 데이터를 조회할 수 있다고 생각했다. 아래 댓글을 보고 이해할 수 있었다.
