Jpa Web Application 2
Domain Design
Function Design
- 회원 기능
- 회원 등록
- 회원 조회
- 상품 기능
- 상품 등록
- 상품 수정
- 상품 조회
- 주문 기능
- 상품 주문
- 주문 내역 조회
- 주문 취소
- 기타 요구사항
- 상품은 재고 관리가 필요하다.
- 상품의 종류는 도서, 음반, 영화가 있다.
- 상품을 카테고리로 구분할 수 있다.
Entity Design
연관관계 설정
- 회원과 주문: 다대일 양방향 관계를 가진다. 회원 객체가 외래키를 가지기 때문에 회원 객체를 연관관계의 주인으로 설정해서 연관관계를 매핑한다.
- 주문상품과 주문: 다대일 양방향 관계로 외래키를 가지는 주문상품을 연관관계를 주인으로 설정한다.
- 주문상품과 상품: 다대일 단방향 관계
- 주문과 배송: 일대일 양방향 관계로 설정하고, 연관관계의 주인은 주문 객체로 설정한다.
- 카테고리와 상품은 다대다 관계를 가진다.
Table Design
테이블 설계를 확인해보면 entity에 임베디드 타입으로 설정된 Address 객체에 대해서 컬럼단위로 풀어서 입력되는 것을 확인할 수 있다. 또한, Item 객체는 Album, Book, Movie 3가지 subclass을 가지는데, Single Table 전략을 취해서 DTYPE을 구분한다.
Entity JPA Codes
Member
@Entity
@Data
public class Member {
@Id
@GeneratedValue
@Column(name="member_id")
private Long id;
private String name;
@Embedded
private Address address;
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<Order> orders = new ArrayList<>();
}
Order
@Entity
@Table(name = "ORDERS")
@Data
public class Order {
@Id
@GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="member_id")
private Member member;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems=new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name="delivery_id")
private Delivery delivery;
private LocalDateTime orderDate;
@Enumerated(EnumType.STRING)
private OrderStatus status;
//연관관계 설정 메소드
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);
}
}
Delivery
@Entity
@Data
public class Delivery {
@Id
@GeneratedValue
@Column(name="delivery_id")
private Long id;
@OneToOne(mappedBy = "delivery",fetch = FetchType.LAZY)
private Order order;
@Embedded
private Address address;
@Enumerated(EnumType.STRING)
private DeliveryStatus status;
}
OrderItem
@Entity
@Data
public class OrderItem {
@Id
@GeneratedValue
@Column(name = "order_item_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="order_id")
private Order order;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="item_id")
private Item item;
private int orderPrice;
private int count;
}
Item, Album, Book, Movie
@Entity
@Data
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="dtype")
public abstract class Item {
@Id
@GeneratedValue
@Column(name="item_id")
private Long id;
private String name;
@ManyToMany(mappedBy = "items", cascade = CascadeType.ALL)
private List<Category> categories = new ArrayList<>();
private int price;
private int stockQuantity;
}
@Entity
@Data
@DiscriminatorValue("A")
public class Album extends Item{
private String artist;
private String etc;
}
@Entity
@Data
@DiscriminatorValue("B")
public class Book extends Item{
private String author;
private String isbn;
}
@Entity
@Data
@DiscriminatorValue("M")
public class Movie extends Item{
private String director;
private String actor;
}
Category
@Entity
@Data
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 = FetchType.LAZY)
@JoinColumn(name="parent_id")
private Category parent;
@OneToMany(mappedBy = "parent")
private List<Category> child = new ArrayList<>();
//연관관계 설정 메소드
public void addChildCategory(Category category) {
this.child.add(category);
category.setParent(this);
}
}
Design Policies?
- 실무에서는 절대 다대다 매핑을 사용하지 말것
위에서 Item과 Category에 대해 다대다(@ManyToMany) 매핑을 활용하였는데, FK-PK 매핑만 설정할 수 있다, 따로 파생되는 컬럼을 처리할 수 없고, 세밀한 쿼리 수행이 어렵기 때문에 Order 와 Item 간에 관계를 매핑하기 위해 OrderItem 이라는 entity를 생성하여 다대일(@ManyToOne), 일대다(@OneToMany) 관계로 풀어서 매핑하는 것이 좋다
- setter는 가급적 활용하지 않는다.
setter을 public으로 열어놓게 되면 변경의 시점이 매우 분산되기 때문에 변경을 제어하기 어려워진다. 따라서 setter 대신, business method을 활용하여 수정을 진행한다. getter은 모두 열어놔도 상관없다
- 연관된 컬럼(필드)를 설정할 때 항상 FetchType.LAZY로 설정한다.
다시 말해, 즉시로딩 대신에 지연로딩으로 설정해서 해당 필드에 접근할 때 연관된 정보를 받아오는 방식을 활용한다. 즉시로딩으로 설정하게 되면 모든 연관된 정보를 가져오기 때문에 정보가 필요하지 않음에도 정보를 가져오는 상황이 발생하여 성능 상 문제가 된다. 또한 N+1 문제를 발생시키기도 하기 때문에 모든 연관된 컬럼에 대해서는 lazy(지연로딩)으로 설정한다.
- collection type에 대해서는 필드 초기화를 진행한다.
JPA에는 List -> PersistentBag, Set -> PersistentSet 등과 같이 내장객체로 변환하기 때문에 필드 단계에서 초기화를 진행한다.부가적으로 객체 생성시 NPE 문제를 예방할 수 있다.
References
link: inflearn
link:jpa
link:thymeleaf
link: springboot3
댓글남기기