[JPA 활용1] 주문 도메인 개발

23272 단어 TILSpringbootJPAJPA

[ 주문, 주문상품 엔티티 개발 ]

1. Order.java

public class Order {

    ...
    
    //==생성 메서드==//
    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 = 0;
        for (OrderItem orderItem : orderItems) {
            totalPrice += orderItem.getTotalPrice();
        }
        return totalPrice;
    }
}

생성 메서드 ( createOrder() )

  • 주문 엔티티를 생성할 때 사용한다.
  • 주문 회원, 배송 정보, 주문 상품의 정보를 받아서 실제 주문 엔티티를 생성한다.

주문 취소 ( cancel() )

  • 주문 취소시 사용한다.
  • 주문 상태를 취소로 변경하고 주문 상품에 주문 취소를 알린다.
  • 만약 이미 배송을 완료한 상품이면 주문을 취소하지 못하도록 예외를 발생시킨다.

전체 주문 가격 조회

  • 주문 시 사용한 전체 주문 가격을 조회한다. 전체 주문 가격을 알려면 각각의 주문 상품 가격을 알아야 한다.
  • 로직을 보면 연관된 주문상품들의 가격을 조회해서 더한 값을 반환한다. (실무에서는 주로 주문에 전체 주문 가격 필드를 두고 역정규화 한다.)

2. OrderItem.java

public class OrderItem {

    ...
    
    //==생성 메서드==//
    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() )

  • 주문 가격 * 수량을 반환한다.

[ 주문 리포지토리 개발 ]

OrderRepository.java

@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) { ... }
}
  • 주문 리포지토리는 주문 엔티티를 저장하고 (save()) 검색하는 (findOne(), findAll()) 기능이 있다.
  • findAll()은 뒤에서 자세히 알아볼 예정!

[ 주문 서비스 개발 ]

OrderService.java

@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());
        delivery.setStatus(DeliveryStatus.READY);

        //주문상품 생성
        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라는 검색 조건을 가진 객체로 주문 엔티티를 검색한다.
  • 뒤에서 더 자세히 알아볼 예정!

📌 참고

  • 도메인 모델 패턴
    : 엔티티가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 것을 뜻한다.
    • 주문 서비스의 주문(order())과 주문 취소(cancelOrder()) 메서드를 보면 비즈니스 로직 대부분이 엔티티에 있다.
    • 서비스 계층은 단순히 엔티티에 필요한 요청을 위임하는 역할을 한다.
  • 트랜잭션 스크립트 패턴
    : 엔티티에는 비즈니스 로직이 거의 없고 서비스 계층에서 대부분의 비즈니스 로직을 처리하는 것

[ 주문 기능 테스트 ]

테스트 요구사항

  • 상품 주문이 성공해야 한다.
  • 상품을 주문할 때 재고 수량을 초과하면 안 된다.
  • 주문 취소가 성공해야 한다.

🔗 테스트 코드 확인하기

실행 결과


[ 주문 검색 기능 개발 ]

💡 JPA로 동적쿼리를 해결하는 방법

1. 문자열 조합: 조건에 따라 문자열을 결합하면서 query문을 만들고 parameter를 세팅해주는 방법
2. JPA Criteria: JPA 표준 스펙에서 제공하는 기능
3. queryDSL: 오픈소스를 통해 제공되는 기능으로 쿼리 구현을 method로 한다.

🔗 코드 확인하기

좋은 웹페이지 즐겨찾기