LostCatBox

Advenced-JPA1-CH02

Word count: 938Reading time: 5 min
2023/05/20 Share

해당 강의 이후부터는 내용 전부가 아닌 핵심 부분들만 분석해서 팁으로 남길예정이다.

기능 요구사항

회원 기능

  • 회원 등록
  • 회원 조회

상품 기능

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

주문 기능 상품 주문

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

기타 요구사항

  • 상품은 재고 관리가 필요하다.
  • 상품의 종류는 도서, 음반, 영화가 있다.
  • 상품을 카테고리로 구분할 수 있다.

도메인 모델과 테이블 설계

스크린샷 2023-05-20 오후 3.16.17

엔티티

스크린샷 2023-05-20 오후 3.16.54

회원 테이블

  • MTM은 결국 FK 두개가 모이는 테이블이 따로 생길수밖에없다
  • 연관관계 주인만 데이터 변경가능, 하인은 조회만 가능
  • 항상 FK가 있는 곳에 일대다 중 를 줘야한다.

스크린샷 2023-05-20 오후 5.39.58

주요한 부분

  • Item 인터페이스 -> DiscriminatorColumn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
@Getter
@Setter
public abstract class Item {
@Id
@GeneratedValue
@Column(name = "order_item_id")
private Long id;

private String name;

private int price;
private int stockQuantity;

}

@Entity
@Getter
@Setter
@DiscriminatorValue("B")
public class Book extends Item{
private String author;
private String ibsc;
}
  • OneToOne, OneToMany

    • 엔티티 관계 주인은 반드시 FK잇는쪽으로 하자
  • EnumType 의 DB저장

    • Order

    • 
      @Entity
      @Table(name = "orders")
      @Getter
      @Setter
      public class Order {
          @Id @GeneratedValue
          @Column(name = "order_id")
          private Long id;
      
          @ManyToOne
          @JoinColumn(name = "member_id")
          private Member member;
      
          @OneToMany(mappedBy = "order")
          private List<OrderItem> orderItems = new ArrayList<>();
      
          @OneToOne
          @JoinColumn(name = "delivery_id") // 1:1 관계 주인!!
          private Delivery delivery;
      
          private LocalDateTime orderDate;
      
          private OrderStatus status; // 주문 상태 ORDER , CANCEL
      
      
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

- ManyToMany는 다음과같이 구성 하지만 추천 X, 이어주는 테이블에 정보 추가못하므로

- ```java
package jpabook.jpashop.domain.item;

import jpabook.jpashop.domain.Category;
import lombok.Getter;
import lombok.Setter;
import org.springframework.context.annotation.Description;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
@Getter
@Setter
public abstract class Item {
@Id
@GeneratedValue
@Column(name = "order_item_id")
private Long id;

private String name;

private int price;
private int stockQuantity;

@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<>();


}
  • package jpabook.jpashop.domain;
    
    import jpabook.jpashop.domain.item.Item;
    import lombok.Getter;
    import lombok.Setter;
    
    import javax.persistence.*;
    import java.util.ArrayList;
    import java.util.List;
    
    @Entity
    @Getter
    @Setter
    public class Category {
        @Id
        @GeneratedValue
        private long id;
    
        private String name;
    
        @ManyToMany
        @JoinTable(name = "category_item", joinColumns = @JoinColumn(name = "category_id"),
        inverseJoinColumns = @JoinColumn(name = "item_id"))
        private List<Item> items = new ArrayList<>();
    
    
    
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

- 한 엔티티에서 부모 자식 만들수있음

- ```java
package jpabook.jpashop.domain;

import jpabook.jpashop.domain.item.Item;
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
public class Category {
@Id
@GeneratedValue
private long id;

private String name;

@ManyToMany
@JoinTable(name = "category_item", joinColumns = @JoinColumn(name = "category_id"),
inverseJoinColumns = @JoinColumn(name = "item_id"))
private List<Item> items = new ArrayList<>();

@ManyToOne
@JoinColumn(name = "parent_id")
private Category parent;

@OneToMany(mappedBy = "parent")
private List<Category> child = new ArrayList<>();
}
  • 엔티티 클래스 주의할점

id는 pk컬럼명_id 사용

  • 엔티티의 식별자는 id 를 사용하고 PK 컬럼명은 member_id 를 사용했다. 엔티티는 타입(여기서는 Member )이 있으므로 id 필드만으로 쉽게 구분할 수 있다. 테이블은 타입이 없으므로 구분이 어렵다. 그리고 테이블은 관례상 테이블명 + id 를 많이 사용한다. 참고로 객체에서 id 대신에 memberId 를 사용해도 된다. 중요한 것은 일관성이다.

실무에서는 @ManyToMany 를 사용하지 말자

  • @ManyToMany 는 편리한 것 같지만, 중간 테이블( CATEGORY_ITEM )에 컬럼을 추가할 수 없고, 세밀하게 쿼리를 실행하기 어렵기 때문에 실무에서 사용하기에는 한계가 있다. 중간 엔티티( CategoryItem 를 만들고 @ManyToOne , @OneToMany 로 매핑해서 사용하자. 정리하면 대다대 매핑을 일대다, 다대일 매핑으로 풀어내서 사용하자.

값 타입은 변경 불가능하게 설계해야 한다.

  • @Setter 를 제거하고, 생성자에서 값을 모두 초기화해서 변경 불가능한 클래스를 만들자. JPA 스펙상 엔티티나 임베디드 타입( @Embeddable )은 자바 기본 생성자(default constructor)를 public 또는 protected 로 설정해야 한다. public 으로 두는 것 보다는 protected 로 설정하는 것이 그나마 더 안전하다. JPA가 이런 제약을 두는 이유는 JPA 구현 라이브러리가 객체를 생성할 때 리플랙션 같은 기술을 사용할 수 있도록 지원해야 하기 때문이다

  • @Embeddable
        @Getter
        public class Address {
            private String city;
            private String street;
            private String zipcode;
            protected Address() {
            }
            public Address(String city, String street, String zipcode) {
                this.city = city;
                this.street = street;
                this.zipcode = zipcode;
             } 
    }
    <!--3-->
    

테이블, 컬럼명 생성 전략

  • 참조1, 참조2

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

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

    1. 카멜 케이스 -> 언더스코어(memberPoint member_point)
    2. .(점) -> _(언더스코어)
    3. 대문자 -> 소문자
  • 스크린샷 2023-05-21 오후 2.30.23

cascade

  • @Entity
    @Table(name = "orders")
    @Getter
    @Setter
    public class Order {
            @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
        private List<OrderItem> orderItems = new ArrayList<>();
    }
    <!--4-->
    

양방향은 항상 연관관계 편의 메서드 만들자!!!

  • 원래는 연관관계를 신경 써줘야하는 부분을 메서드에 포함시키므로써 편하게 관리할수있도록해준다.

  • Member member = new Member();
    Order order = new Order();
    member.getOrders().add(order); // member 엔티티의 orders에 넣어줫더라도, order 객체에도 해당 member_id를 알려줘야한다.
    order.setMember(member); 
    <!--5-->
  • 결과적으로 order.setMember(member) 를 통해 손쉽게 추가되며 연관관계도 설정한다.

구현할것

  • 회원가입, 회원목록
  • 상품 등록, 상품 목록
  • 상품 주문, 주문 내역

스크린샷 2023-05-31 오후 8.12.51

  • 주의: 방향은 무조건 단방향으로만 하자
  • Service-> repository-> web-> api -> 성능최적화 순으로 진행

회원 도메인 개발

  • 회원 등록
  • 회원 목록 조회

주요 내용

  • Field Injection, Setter Injection, Construct Injection 차이 알기

    • @RequiredArgsConstructor 권장
  • 쓰레드 safe 하기위해 DB에도 제약조건 반드시 설정하자

    • // 해당 부분은 쓰레드 세이프 하지않다. 따라서 DB에도 제약 unique로 확보하자
      private void validateDuplicateMember(Member member) {
          List<Member> findMembers = memberRepository.findByName(member.getName());
      
          if (!findMembers.isEmpty()) {
              throw new IllegalStateException("이미 존재하는 회원입니다");
          }
      }
      <!--6-->
  • h2 인메모리 모드로 테스트하고싶다면

    • url: jdbc:h2:mem:test
    • 또는 아예 h2 datasource 설정 자체를 없애면 memory모드로 디폴트로 실행된다.
CATALOG
  1. 1. 해당 강의 이후부터는 내용 전부가 아닌 핵심 부분들만 분석해서 팁으로 남길예정이다.
  2. 2. 기능 요구사항
    1. 2.1. 도메인 모델과 테이블 설계
    2. 2.2. 엔티티
    3. 2.3. 회원 테이블
    4. 2.4. 주요한 부분
  3. 3. 엔티티 클래스 주의할점
    1. 3.1. id는 pk컬럼명_id 사용
    2. 3.2. 실무에서는 @ManyToMany 를 사용하지 말자
    3. 3.3. 값 타입은 변경 불가능하게 설계해야 한다.
    4. 3.4. 테이블, 컬럼명 생성 전략
    5. 3.5. cascade
    6. 3.6. 양방향은 항상 연관관계 편의 메서드 만들자!!!
  4. 4. 구현할것
  5. 5. 회원 도메인 개발
    1. 5.1. 주요 내용