해당 강의 이후부터는 내용 전부가 아닌 핵심 부분들만 분석해서 팁으로 남길예정이다.
기능 요구사항
회원 기능
- 회원 등록
- 회원 조회
상품 기능
- 상품 등록
- 상품 수정
- 상품 조회
주문 기능 상품 주문
- 주문 내역 조회
- 주문 취소
기타 요구사항
- 상품은 재고 관리가 필요하다.
- 상품의 종류는 도서, 음반, 영화가 있다.
- 상품을 카테고리로 구분할 수 있다.
도메인 모델과 테이블 설계
엔티티
회원 테이블
- MTM은 결국 FK 두개가 모이는 테이블이 따로 생길수밖에없다
- 연관관계 주인만 데이터 변경가능, 하인은 조회만 가능
- 항상 FK가 있는 곳에 일대다 중 다 를 줘야한다.
주요한 부분
- Item 인터페이스 ->
DiscriminatorColumn
1 |
|
OneToOne, OneToMany
- 엔티티 관계 주인은 반드시 FK잇는쪽으로 하자
EnumType 의 DB저장
Order
"orders") public class Order { (name = "order_id") private Long id; (name = "member_id") private Member member; (mappedBy = "order") private List<OrderItem> orderItems = new ArrayList<>(); (name = "delivery_id") // 1:1 관계 주인!! private Delivery delivery; private LocalDateTime orderDate; private OrderStatus status; // 주문 상태 ORDER , CANCEL
(name =
}
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; public class Category { private long id; private String name; (name = "category_item", joinColumns = (name = "category_id"), inverseJoinColumns = (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 구현 라이브러리가 객체를 생성할 때 리플랙션 같은 기술을 사용할 수 있도록 지원해야 하기 때문이다
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-->
테이블, 컬럼명 생성 전략
스프링 부트에서 하이버네이트 기본 매핑 전략을 변경해서 실제 테이블 필드명은 다름
스프링 부트 신규 설정 (엔티티(필드) 테이블(컬럼))
- 카멜 케이스 -> 언더스코어(memberPoint member_point)
- .(점) -> _(언더스코어)
- 대문자 -> 소문자
cascade
"orders") public class Order { (mappedBy = "order", cascade = CascadeType.ALL) private List<OrderItem> orderItems = new ArrayList<>(); } <!--4-->
(name =
양방향은 항상 연관관계 편의 메서드 만들자!!!
원래는 연관관계를 신경 써줘야하는 부분을 메서드에 포함시키므로써 편하게 관리할수있도록해준다.
Member member = new Member(); Order order = new Order(); member.getOrders().add(order); // member 엔티티의 orders에 넣어줫더라도, order 객체에도 해당 member_id를 알려줘야한다. order.setMember(member); <!--5-->
결과적으로
order.setMember(member)
를 통해 손쉽게 추가되며 연관관계도 설정한다.
구현할것
- 회원가입, 회원목록
- 상품 등록, 상품 목록
- 상품 주문, 주문 내역
- 주의: 방향은 무조건 단방향으로만 하자
- 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모드로 디폴트로 실행된다.