1️⃣ 회원 기능
2️⃣ 상품 기능
3️⃣ 주문 기능
4️⃣ 기타 요구사항
1️⃣ 회원, 주문, 상품의 관계
2️⃣ 상품 분류
1️⃣ 회원(Member)
2️⃣ 주문(Order)
3️⃣ 주문상품(OrderItem)
4️⃣ 상품(Item)
5️⃣ 배송(Delivery)
6️⃣ 카테고리(Category)
7️⃣ 주소(Address)
1️⃣ MEMBER
2️⃣ ITEM
1️⃣ 회원과 주문
Order.member
를 ORDERS.MEMBER_ID
외래 키와 매핑한다.2️⃣ 주문상품과 주문
OrderItem.order
를 ORDER_ITEM.ORDER_ID
외래 키와 매핑한다.3️⃣ 주문상품과 상품
OrderItem.item
을 ORDER_ITEM.ITEM_ID
외래 키와 매핑한다.4️⃣ 주문과 배송
= 일대일 양방향 관계이다.
Order.delivery
를 ORDERS.DELIVERY_ID
외래 키와 매핑한다.5️⃣ 카테고리와 상품
@ManyToMany
를 사용해서 매핑한다.@ManyToMany
는 사용하지 말자. 여기서는 다대다 관계를 예제로 보여주기 위해 추가했을 뿐!)📌 참고
- 예제에서는 설명을 쉽게하기 위해 엔티티 클래스에
Getter
,Setter
를 모두 열고, 최대한 단순하게 설계- 실무에서는 가급적
Getter
는 열어두고,Setter
는 꼭 필요한 경우에만 사용하는 것을 추천!!
Member
- 회원 엔티티package jpabook.jpashop.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String name;
@Embedded
private Address address;
@JsonIgnore
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
Order
- 주문 엔티티package jpabook.jpashop.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.BatchSize;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import static javax.persistence.FetchType.*;
@Entity
@Table(name = "orders")
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {
@Id @GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "member_id")
private Member member;
@JsonIgnore
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
@JsonIgnore
@OneToOne(fetch = LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "delivery_id")
private Delivery delivery;
private LocalDateTime orderDate; //주문시간
// Enum 타입은 @Enumerated(EnumType.STRING) 꼭 필요! -> 문자열로 출력
@Enumerated(EnumType.STRING)
private OrderStatus status; //주문상태 [ORDER, CANCEL]
// 양방향 일 때 -> 연관관계 편의 메서드
public void setMember(Member member) {
this.member = member;
member.getOrders().add(this);
}
public void addOrderItem(OrderItem orderItem) {
orderItems.add(orderItem);
orderItem.setOrder(this);
}
public void setDelivery(Delivery delivery) {
this.delivery = delivery;
delivery.setOrder(this);
}
}
OrderItem
- 주문 상품 엔티티package jpabook.jpashop.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jpabook.jpashop.domain.item.Item;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import static javax.persistence.FetchType.*;
@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OrderItem {
@Id @GeneratedValue
@Column(name = "order_item_id")
private Long id;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "item_id")
private Item item;
@JsonIgnore
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "order_id")
private Order order;
private int orderPrice; //주문 가격
private int count; //주문 수량
//==생성 메서드==//
public static OrderItem createOrderItem(Item item, int orderPrice, int count) {
OrderItem orderItem = new OrderItem();
orderItem.setItem(item);
orderItem.setOrderPrice(orderPrice);
orderItem.setCount(count);
item.removeStock(count);
return orderItem;
}
}
Item
- 상품 엔티티package jpabook.jpashop.domain.item;
import jpabook.jpashop.domain.Category;
import jpabook.jpashop.exception.NotEnoughStockException;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.BatchSize;
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 = "item_id")
private Long id;
private String name;
private int price;
private int stockQuantity;
@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<>();
}
Delivery
- 배송 엔티티package jpabook.jpashop.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import static javax.persistence.FetchType.*;
@Entity
@Getter @Setter
public class Delivery {
@Id @GeneratedValue
@Column(name = "delivery_id")
private Long id;
@JsonIgnore
@OneToOne(mappedBy = "delivery", fetch = LAZY)
private Order order;
@Embedded
private Address address;
// Enum 타입은 @Enumerated(EnumType.STRING) 꼭 필요! -> 문자열로 출력
@Enumerated(EnumType.STRING)
private DeliveryStatus status; //READY, COMP
}
Category
- 카테고리 엔티티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;
import static javax.persistence.FetchType.*;
@Entity
@Getter @Setter
public class Category {
@Id @GeneratedValue
@Column(name = "category_id")
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(fetch = LAZY)
@JoinColumn(name = "parent_id")
private Category parent;
// 내 자식 - 자식은 여러명을 가질 수 있음
@OneToMany(mappedBy = "parent")
private List<Category> child = new ArrayList<>();
//==연관관계 메서드==//
public void addChildCategory(Category child) {
this.child.add(child);
child.setParent(this);
}
}
📌 실무에서는
@ManyToMany
를 사용하지 말자
@ManyToMany
는 편리한 것 같지만, 중간 테이블(CATEGORY_ITEM
)에 컬럼을 추가할 수 없고, 세밀하게 쿼리를 실행하기 어렵기 때문에 실무에서 사용하기에는 한계가 있다.- 중간 엔티티(
CategoryItem
)를 만들고@ManyToOne
,@OneToMany
로 매핑해서 사용하자!- 정리하면 다대다 매핑을 일대다, 다대일 매핑으로 풀어 내서 사용하자!!
Address
- 주소 값 타입package jpabook.jpashop.domain;
import lombok.Getter;
import javax.persistence.Embeddable;
// 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;
}
}
📌 값 타입은 변경 불가능하게 설계해야 한다!
@Setter
를 제거하고, 생성자에서 값을 모두 초기화해서 변경 불가능한 클래스를 만들자!- JPA 스펙상 엔티티나 임베디드 타입(
@Embeddable
)은 자바 기본 생성자(default constructor
)를public
또는protected
로 설정해야 한다.public
으로 두는것 보다는protected
로 설정하는 것이 그나마 더 안전하다!
Setter
가 모두 열려있다면, 변경 포인트가 너무 많아서 유지보수가 어렵다☹️ Setter
제거하자!EAGER
)은 예측이 어렵고, 어떤 SQL이 실행될지 추적하기 어렵다.@XToOne(OneToOne, ManyToOne)
관계는 기본이 즉시 로딩 이므로 직접 지연 로딩으로 설정해야 한다!private List<Order> orders = new ArrayList<>();