(가장 중요한 주문 도메인)
→ 왜냐? : 여기에 비즈니스 로직들이 얽혀서 돌아가는걸 jpa나 엔티티를 가지고 어떻게 풀어내는지 알 수 있고, 트랜잭션 스크립트 패턴과 도메인 모델 패턴을 많이 못 접해봤을 것이다. 엔티티에 실제 비즈니스 로직이 있고 더 많은 것을 엔티티로 위임하고 이런 스타일인데 그런 것들이 어떻게 되는지 코드를 통해서 이해해 볼 수 있다. (주문이 제일 복잡)
package com.jpabook.jpashop.domain;
import com.jpabook.jpashop.domain.Member;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import static jakarta.persistence.FetchType.LAZY;
@Entity
@Table(name = "orders")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {
@Id
@GeneratedValue //(strategy = GenerationType.IDENTITY) 수정 0124
@Column(name = "order_id")
private Long id;
@ManyToOne(fetch = LAZY) // 연관관계 주인이기 떄문에 이대로 둔다.
@JoinColumn(name = "member_id")
private Member member; // 주문 회원
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL) // 매핑이 없으면 빨간 줄이 뜸. -> 당연
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne(cascade = CascadeType.ALL, fetch = LAZY)//01.24 수정
@JoinColumn(name = "delivery_id")
private Delivery delivery; // 배송정보
private LocalDateTime orderDate; //주문시간
@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);
}
//==생성 메서드==//
// 주문 생성에 대한 복잡한 비즈니스 로직을 다 여기에 이렇게 응집해 놓는거
// 그래서 앞을 ㅗ뭔가 주문을 생성하는 거 관련되면 여기를 딱 들어가서 여기만 고치면 되는 아주 편리한 로직이 탄생했다.
public static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems) {
Order order = new Order();
order.setMember(member);
order.setDelivery(delivery);
for (OrderItem orderItem : orderItems) {
order.addOrderItem(orderItem);
}
order.setStatus(OrderStatus.ORDER);
order.setOrderDate(LocalDateTime.now());
return order;
// 이렇게 하면 이 오더가 연관관계를 쫙 걸면서 세팅이 되고
// 심지어 상태랑 주문 시간 정보까지 다 세팅을 해서 이제 뭔가 완전히 정리가 되는 것이다.
// 이런 스타일로 작성하는게 중요하다. -> 왜???
// 앞으로 생성하는 지점을 변경해야 하면, createOrder 만 바꿔주면 되기 때문이다. 이게 중요한 포인트
}
//==비즈니스 로직==//
/**
* 주문 취소
*/
public void cancel() {
if (delivery.getStatus() == DeliveryStatus.COMP) {
throw new IllegalStateException("이미 배송완료된 상품은 취소가 불가능합니다.");
}
this.setStatus(OrderStatus.CANCEL);
for (OrderItem orderItem : orderItems) {
orderItem.cancel();
}
}
//==조회 로직==//
/**
* 전체 주문 가격 조회
*/
public int getTotalPrice() {
/*int totalPrice = 0;
for (OrderItem orderItem : orderItems) {
totalPrice += orderItem.getTotalPrice();
}
return totalPrice;*/
// 람다 활용
return orderItems.stream()
.mapToInt(OrderItem::getTotalPrice)
.sum();
}
}
createOrder()
)
주문 엔티티를 생성할 때 사용한다. 주문 회원, 배송정보, 주문상품의 정보를 받아서 실제 주문 엔티티를 생성한다cancel()
)
주문 취소 시 사용한다. 주문 상태를 취소로 변경하고 주문상품에 주문 취소를 알린다.
만약 이미 배송을 완료한 상품이면 주문을 취소하지 못하도록 예외를 발생시킨다.getTotalPrice()
)
주문 시 사용한 전체 주문 가격을 조회한다.
전체 주문 가격을 알려면 각각의 주문상품 가격을 알아야 한다.
로직을 보면 연관된 주문상품들의 가격을 조회해서 더한 값을 반환한다.
(실무에서는 주로 주문에 전체 주문 가격 필드를 두고 역정규화 한다)package com.jpabook.jpashop.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.jpabook.jpashop.domain.item.Item;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import static jakarta.persistence.FetchType.*;
@Entity
@Table(name = "order_item")
@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; // 주문 상품
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "order_id")
@JsonIgnore // 0124 add
private Order order; // 주문
private int orderPrice;// 주문가격
private int count; //주문 수량
//protected OrderItem(){} 이거 대신 어노테이션으로 @NoArgsConstructor(access = AccessLevel.PROTECTED) 이렇게 자동생성해줌,
//==생성 메서드 ==//
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;
}
//==비즈니스 로직==//
/** 주문 취소 */
public void cancel() {
//재고를 주문수량만큼 원복해야함
getItem().addStock(count);
}
//==조회 로직==//
/** 주문상품 전체 가격 조회*/
public int getTotalPrice() {
// 주문할 때, 주문 수량과 가격은 곱해야하기 떄문
return getCount() * getOrderPrice();
}
}
createOrderItem()
주문 상품, 가격, 수량 정보를 사용해서 주문상품 엔티티를 생성한다.
그리고 item.removeStock(count)
를 호출해서 주문한 수량만큼 상품의 재고를 줄인다.cancel()
getItem().addStock(count)
를 호출해서 취소한 주문 수량만큼 상품의 재고를 증가시킨다.getTotalPrice()
주문 가격에 수량을 곱한 값을 반환package jpabook.jpashop.repository;
import jpabook.jpashop.domain.Order;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
@Repository
@RequiredArgsConstructor
public class OrderRepository {
private final EntityManager em;
public void save(Order order) {
em.persist(order);
}
public Order findOne(Long id) {
return em.find(Order.class, id);
} }
주문 리포지토리에는 주문 엔티티를 저장하고 검색하는 기능이 있다. 마지막의 findAll(OrderSearch orderSearch)
메서드는 조금 뒤에 있는 주문 검색 기능에서 자세히 알아보자.
→ 영한님 코드. 내 코드를 올리고 싶었으나, 너무 많이 개발해둬서, 해당 챕터에서 사용하기엔 부적절하다고 판단.