주문 도메인 개발

2023. 1. 5. 15:20·BackEnd/실전! 스프링 부트와 JPA 활용1

구현 기능은 상품 주문, 주문 내역 조회, 주문 취소가 있다.

구현 순서는 주문 엔티티, 주문상품 엔티티 개발 -> 주문 리포지토리 개발 -> 주문 서비스 개발 -> 주문 검색 기능 개발 -> 주문 기능 테스트 순이다.

 

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

  • 주문 생성 메서드
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;
}

Order 엔티티에 주문 생성 매서드를 만들어 한번에 주문을 만든다.

  • 비즈니스 로직
/**
 * 주문 취소
 */
public void cancel(){
    if(delivery.getStatus() == DeliveryStatus.COMP){
        throw new IllegalStateException("이미 배송완료된 상품은 취소가 불가능합니다.");
    }
    this.setStatus(OrderStatus.CANCEL);
    for (OrderItem orderItem : orderItems) {
        orderItem.cancel();
    }
}

주문 취소 로직이다. 주문이 이미 배송 완료된 상태이면 최소를 못하게 했고 아니면 취소 상태로 바꿔주었다.

주문이 취소되면 재고를 올려야하므로 orderItem에 cancel이라는 비즈니스 로직을 만들었다.

//orderItem Entity
public void cancel() { getItem().addStock(count); }
  • 조회 로직
/**
 * 전체 주문 가격 조회
 */
public int getTotalPrice() {
    int totalPrice = 0;
    for (OrderItem orderItem : orderItems) {
        totalPrice += orderItem.getTotalPrice();
    }
    return totalPrice;
}

전체 주문 가격을 조회하는 것이다. 모든 orderItem의 가격을 가져와 totalPrice에 더해주었다. 

//orderItem Entity
public int getTotalPrice() { return getOrderPrice() * getCount(); }
  • 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;
}

 

주문 리포지토리 개발

@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(){   }
}

 

주문 서비스 개발

  • 주문 하기
@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();
}

Order와 OrderItem을 직접 생성하게 되면 여러 개발자가 사용한다고 할 때 여러 방식으로 만들어지는 문제가 생길 수 있다. 그래서 생성 메서드를 만들고 위에 아래의 코드를 추가해준다. 그러면 그냥 생성으로는 만들 수 없다. 

@NoArgsConstructor(access = AccessLevel.PROTECTED)
  • 주문 취소
@Transactional
public void cancelOrder(Long orderId){
    //주문 엔티티 조회
    Order order = orderRepository.findOne(orderId);
    //주문 취소
    order.cancel();
    //변경 내역 감지로 인해 데이터베이스에 쿼리가 자동으로 나간다. JPA의 강점
}

 

  • 도메인 모델 패턴 : 엔티티가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 것(위의 예제)
  • 트랜잭션 스크립트 패턴 : 엔티티에는 비즈니스 로직이 거의 없고 서비스 계층에서 대부분의 비즈니스 로직을 처리하는 것

 

주문 기능 테스트

테스트할 기능

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

 

  • 상품 주문
@Test
public void 상품주문() throws Exception{
    Member member = new Member();
    member.setName("회원1");
    member.setAddress(new Address("서울", "강가", "123-123"));
    em.persist(member); //회원 생성

    Book book = new Book();
    book.setName("시골 JPA");
    book.setPrice(10000);
    book.setStockQuantity(10);
    em.persist(book); //책 생성

    int orderCount = 2;

    Long orderId = orderService.order(member.getId(), book.getId(), orderCount);// 책 주문

    Order getOrder = orderRepository.findOne(orderId); // 주문 가져오기

    assertEquals(OrderStatus.ORDER, getOrder.getStatus(), "상품 주문시 상태는 ORDER");
    assertEquals(1, getOrder.getOrderItems().size(), "주문한 상품 종류 수가 정확해야 한다.");
    assertEquals(10000*orderCount, getOrder.getTotalPrice(), "주문 가격은 가격 * 수량이다");
    assertEquals(8, book.getStockQuantity(), "주문 수량만큼 재고가 줄어야 한다.");
}

결과가 성공적으로 나온 모습이다.

 

    Member member = new Member();
    member.setName("회원1");
    member.setAddress(new Address("서울", "강가", "123-123"));
    em.persist(member); //회원 생성

    Book book = new Book();
    book.setName("시골 JPA");
    book.setPrice(10000);
    book.setStockQuantity(10);
    em.persist(book); //책 생성

이 코드들을 각각 ctrl - alt - m을 하면 인텔리제이가 하나의 매소드로 만들어준다. createMember와 createBook으로 만들었다.

 

  • 재고 수량 초과 테스트
@Test(expected = NotEnoughStockException.class)
public void 상품주문_재고수량초과() throws Exception{
    Member member = createMember();
    Item item = createBook("시골 JPA", 10000, 10);
    int orderCount = 11;

    orderService.order(member.getId(), item.getId(), orderCount);

    fail("재고 수량 부족 예외가 발생해야 한다.");
}

지금 재고가 10개가 있는데 만약 11개를 주문했다고 하면 order에서 에러가 발생해야 한다.

expected = NotEnoughStockException.class

이 부분이 이 에러가 발생했는지 알려준다. 

에러가 성공적으로 발생하였다.

 

 

  • 주문 취소
@Test
public void 주문취소() {
    Member member = createMember();
    Book item = createBook("시골 JPA", 10000, 10);

    int orderCount = 2;

    Long orderId = orderService.order(member.getId(), item.getId(), orderCount);

    orderService.cancelOrder(orderId);

    Order getOrder = orderRepository.findOne(orderId);

    assertEquals(OrderStatus.CANCEL, getOrder.getStatus(), "주문 취소시 상태는 CANCEL 이다.");
    assertEquals(10, item.getStockQuantity(), "주문 취소된 상품은 그만큼 재고가 증가해야 한다.");

}

주문 취소시 검사해야할 부분은 2개이다. 취소 되면 상태가 CANCEL로 바뀌는지 또 재고가 원 상태로 복구가 되는지이다.

테스트 결과 모두 성공하였다.

 

 

주문 검색 기능 개발

검색을 할려면 JPA에서 동적 쿼리를 사용해야 한다.

public List<Order> findAll(OrderSearch orderSearch){
    //language=JPQL
    String jpql = "select o From Order o join o.member m";
    boolean isFirstCondition = true;

    //주문 상태 검색
    if (orderSearch.getOrderStatus() != null) {
        if (isFirstCondition) {
            jpql += " where";
            isFirstCondition = false;
        } else {
            jpql += " and";
        }
        jpql += " o.status = :status";
    }

    //회원 이름 검색
    if (StringUtils.hasText(orderSearch.getMemberName())) {
        if (isFirstCondition) {
            jpql += " where";
            isFirstCondition = false;
        } else {
            jpql += " and";
        }
        jpql += " m.name like :name";
    }
    
    TypedQuery<Order> query = em.createQuery(jpql, Order.class)
            .setMaxResults(1000); //최대 1000건
    if (orderSearch.getOrderStatus() != null) {
        query = query.setParameter("status", orderSearch.getOrderStatus());
    }
    if (StringUtils.hasText(orderSearch.getMemberName())) {
        query = query.setParameter("name", orderSearch.getMemberName());
    }
    return query.getResultList();
}

jpql은 기본 상태에서 검색이다.

  • 처음은 주문 상태 검색으로 만약 주문이 첫번째이면 where문 만  추가하고 주문이 여러개이면 and를 붙여 자기 필요한 status를 모두 꺼내온다.
  • 두번째는 회원 이름 검색이다. 주문과 똑같이 원하는 회원의 주문이 한개이면 where만 붙이고 만약 원하는 회원이 주문이 여러개라면 and를 붙여 모두 조사한다.
  •  TypedQuery<Order> query로 쿼리를 만들고 쿼리에 setParameter로 parameter를 추가해준다.

 

강의 출저

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1

 

실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 인프런 | 강의

실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다., - 강

www.inflearn.com

 

'BackEnd > 실전! 스프링 부트와 JPA 활용1' 카테고리의 다른 글

변경 감지와 병합(merge)  (1) 2023.01.06
웹 계층 개발(회원)  (0) 2023.01.06
상품 도메인 개발  (0) 2023.01.04
회원 도메인 개발  (0) 2023.01.04
도메인 분석 설계  (0) 2023.01.03
'BackEnd/실전! 스프링 부트와 JPA 활용1' 카테고리의 다른 글
  • 변경 감지와 병합(merge)
  • 웹 계층 개발(회원)
  • 상품 도메인 개발
  • 회원 도메인 개발
인프라 감자
인프라 감자
  • 인프라 감자
    삶은 인프라
    인프라 감자
  • 전체
    오늘
    어제
    • 분류 전체보기 (243)
      • 클라우드&인프라 (28)
        • 인프라 공부 (4)
        • AWS 구조와 서비스 (18)
        • 클라우드 공부 (4)
        • Terraform (2)
      • AWS Cloud School (13)
        • project (5)
        • Linux, Network (6)
        • Docker (2)
      • BackEnd (162)
        • JAVA 공부 (15)
        • 알고리즘 공부 (71)
        • MySQL 문제 풀기 (8)
        • 스프링 핵심 원리 - 기본편 (18)
        • 스프링 MVC 1편 (4)
        • 자바 ORM 표준 JPA 프로그래밍 (21)
        • 실전! 스프링 부트와 JPA 활용1 (8)
        • 실전! 스프링 부트와 JPA 활용2 (5)
        • 스프링 데이터 JPA (8)
        • Querydsl (4)
      • 혼자하는 프로젝트 (32)
        • 배달의 민족 클론코딩 (7)
        • 나만의 프로젝트 (10)
        • 스프링 부트로 구현한 웹 (15)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • Email
    • GitHub
  • 공지사항

  • 인기 글

  • 태그

    자동 배포
    dp
    linux
    상속
    이것이 자바다
    조합
    디팬스 게임
    완전탐색
    자바
    VPN
    스프링 핵심 원리-기본편
    백트래킹
    네트워크 기본 용어
    프로그래머스
    querydsl
    정렬
    유니온 파인드
    중첩 선언
    다이나믹 프로그래밍
    쿼드 압축
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
인프라 감자
주문 도메인 개발
상단으로

티스토리툴바