이전글
2023.02.23 - [Back-End/JPA] - JPA 활용 - 상품 도메인 개발
주문 도메인 개발
구현 기능
- 상품 주문
- 주문 내역 조회
- 주문 취소
순서
- 주문 엔티티, 주문상품 엔티티 개발
- 주문 리포지토리 개발
- 주문 서비스 개발
- 주문 검색 기능 개발
- 주문 기능 테스트
주문, 주문상품 엔티티 개발
주문 엔티티 코드
package jpabook.jpashop.domain;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "orders")
@Getter @Setter
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<>();
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@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;
}
//==비즈니스 로직==//
/*
* 주문 취소
*/
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 = orderItems.stream()
.mapToInt(OrderItem::getTotalPrice)
.sum();
return totalPrice;
}
}
기능 설명
- 생성 메서드(
createOrder()
) : 주문 엔티티를 생성할 때 사용한다. 주문 회원, 배송정보, 주문상품의 정보를 받아서 실제 주문 엔티티를 생성 - 주문 취소(cancel()) : 주문 취소시 사용, 주문 상태를 취소로 변경하고, 주문 상품에 주문 취소를 알린다. 이미 배송을 완료한 상품이면 주문을 취소하지 못하도록 예외 발생
- 전체 주문 가격 조회 : 주문 시 사용한 전체 주문 가격을 조회한다. 각각의 주문상품 가격 * 개수하여 더한다.(실무에서는 주로 주문에 전체 주문 가격 필드를 두고 역정규화 한다.)
주문상품 엔티티 코드
package jpabook.jpashop.domain;
import jpabook.jpashop.domain.item.Item;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@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 = FetchType.LAZY)
@JoinColumn(name = "item_id")
private Item item; //주문 상품
@ManyToOne(fetch = FetchType.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;
}
//==비즈니스 로직==//
public void cancel() {
getItem().addStock(count);
}
//==조회 로직==//
/**
* 주문상품 전체 가격 조회
*/
public int getTotalPrice() {
return getOrderPrice() * getCount();
}
}
기능 설명
- 생성 메서드(
createOrderItem()
) : 주문 상품, 가격, 수량 정보를 사용해서 주문상품 엔티티를 생성하고,item.removeStock(count)
를 호출해서 수문한 수량만큼 상품의 재고를 줄인다. - 주문 취소(
cancel()
) :getItem().addStock(count)
를 호출해서 취소한 주문 수량만큼 상품의 재고를 증가시킴 - 주문 가격 조회(
getTotalPrice()
) : 주문 가격에 수량을 곱한 값을 반환
💡 NoArgsConstructor(access = AccessLevel.PROTECTED)를 사용한 이유?
현재 주문 엔티티와 주문 상품 엔티티에는 createOrder()
와 createOrderItem()
과 같은 생성 메서드가 존재하는데, 협업 시에, 다른 개발자가 해당 생성 메서드를 사용하지 않고, new Order()
로 생성후, setter를 이용해서 주입하는 형식으로 코드를 짤 수도 있습니다. 이렇게 각각의 생성자와 생성메서드를 이용한 경우 코드들이 얽히면서 유지보수 하기 어려워 지니 기본 생성자 사용을 막고, 생성 메서드를 이용하도록 하기 위함입니다.
주문 리포지토리 개발
package jpabook.jpashop.repository;
import jpabook.jpashop.domain.Order;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import java.util.List;
@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);
}
//나중에 사용할 예정
// public List<Order> findAll(OrderSearch orderSearch) {}
}
주문 서비스 개발
package jpabook.jpashop.service;
import jpabook.jpashop.domain.Delivery;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.domain.Order;
import jpabook.jpashop.domain.OrderItem;
import jpabook.jpashop.domain.item.Item;
import jpabook.jpashop.repository.ItemRepository;
import jpabook.jpashop.repository.MemberRepository;
import jpabook.jpashop.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final MemberRepository memberRepository;
private final ItemRepository itemRepository;
/**
* 주문
*/
@Transactional
public Long order(Long memberId, Long itemId, int count) {
//엔티티 생성
Member member = memberRepository.findOne(memberId);
Item item = itemRepository.findOne(itemId);
//배송 정보 생성
Delivery delivery = new Delivery();
delivery.setAddress(member.getAddress());
//주문상품 생성
OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count);
//주문 생성
Order order = Order.createOrder(member, delivery, orderItem);
//주문 저장
orderRepository.save(order);
return order.getId();
}
/**
* 주문 취소
*/
@Transactional
public void cancelOrder(Long orderId) {
//주문 엔티티 조회
Order order = orderRepository.findOne(orderId);
//주문 취소
order.cancel();
}
//검색
/* public List<Order> findOrders(OrderSearch orderSearch) {
return orderRepository.findAll(orderSearch);
}*/
}
예제를 단순화 하기 위해 한 번에 하나의 상품만 주문 가능하다.
기능 설명
- 주문(order()) : 주문하는 회원 식별자, 상품 식별자, 주문 수량 정보를 받아서 실제 주문 엔티티를 생성한 후 저장한다.
- 주문 취소(cancelOrder()) : 주문 식별자를 받아서 주문 엔티티를 조회한 후 주문 엔티티에 주문 취소를 요청한다.
- 주문 검색 (findOrders()) : OrderSearch라는 검색 조건을 가진 객체로 주문 엔티티를 검색하게 되는데, 이는 뒤에 배울 것
현재 주문 서비스의 주문과 주문 취소 메서드를 보면 비즈니스 로직 대부분이 엔티티에 존재하고, 서비스 계층은 단순히 엔티티에 필요한 요청을 위임하는 역할을 하는데, 이와 같은 엔티티가 비즈니스 로직을 가지는 것을 도메인 모델 패턴이라고 한다. 반대로 엔티티에는 비즈니스 로직이 거의 없고 서비스 계층에서 대부분의 비즈니스 로직을 처리하는 것을 트랜잭션 스크립트 패턴이라고 한다.
참고
반응형