본문 바로가기
Back-End/Spring Data

6.주문도메인개발

by 두두리안 2020. 12. 30.
728x90

6.주문도메인개발

  • 목차
    • 1.주문 주문상품 엔티티 개발
    • 2.주문 리포지토리 개발(Repository)
    • 3.주문 서비스 개발(Service)
    • 4.주문 기능 테스트
    • 5.주문 검색기능

1.주문 주문상품 엔티티 개발

Order

@Entity
@Table(name = "orders")
@Getter @Setter
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;
    }

}

OrderItem

@Entity
@Getter @Setter
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();
    }
}
  • 생성할때 부터 createOrder() 를 호출 생성메서드에서 값을 셋팅해준다
  • 비즈니스 주문취소 로직이 엔티티 안에있다 (도메인 주도설계)
  • OrderItem 생성메서드를 만들어준다
  • orderItem.cancel() 이동하면 OrderItem 에서 addStock 을 불러서 원래상태로 돌려준다
  • 주문가격과 주문수량을 곱한 최종값을 totalPrice 반환하다
  • OrderItem... 이런식으로 넘어오기보다는 DTO 를 만들어서 넘어온다!

2.주문 리포지토리 개발(Repository)

OrderRepository

@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){}
}
  • //public List findAll(OrderSearch orderSearch){} 기능은 나중에!

3.주문 서비스 개발(Service)

  • 상품 주문
  • 주문 취소
  • 주문 내역 조회OrderService (주문)
@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 주문은 저장되기때문에 트랜잭션을 걸어준다
  • 엔티티 조회할때 findOne 이용해서 찾는다
  • 배송정보 생성할때 회원의 주소를 넣어준다
  • 주문상품 생성할때 OrderItem 의 createOrderItem 호출 주문상품과,주문가격,수
  • 주문 생성할때 Order 의 createOrder 를 호출 회원,배송지역,주문상품
  • 주문 저장 save 한다음 order 의 id값 을 반환한다
  • orderRepository.save(order); -> CascadeType.ALL 이기때문에 OrderItem 과 Delivery 도 같이 persist 된다
  • OrderItem 과 Delivery 가 다른데에서도 쓰일때 별도의 리포지토리를 만들어서 persist 해줘야한다
  • 다른데에서 참조하지 않을때 CascadeType.ALL 쓴다!
  • 이번 Order 는 Order 만 OrderItem 과 Delivery 를 사용하기때문에 CascadeType.ALL 쓴것이다!
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OrderService {
    //주문상품 생성
    OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count);

    //OrderItem orderItem1=new OrderItem();

    protected OrderItem() {

    }

}
  • OrderItem orderItem1=new OrderItem(); 을 막아주기위해서 OrderItem 에서 protected 생성자를 만들어준다
  • @NoArgsConstructor(access = AccessLevel.PROTECTED) 롬복을 이용해도 된다
  • 코드를 항상 제약하는 형식으로 코딩한다

OrderService (주문취소)

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final MemberRepository memberRepository;
    private final ItemRepository itemRepository;

    //취소
    @Transactional
    public void cancelOrder(Long orderId){

        //주문 엔티티 조회
        Order order=orderRepository.findOne(orderId);

        //주문취소
        order.cancel();
    }
}
  • order -> cancel -> OrderStatus.CANCEL -> OrderItem -> cancel -> addStock
  • JPA 변경을 감지해서 (더티체킹) 데이터베이스에 Update 쿼리가 나간다
  • 도메인 모델 패턴 : 서비스 계층은 단순히 엔티티에 필요한 요청을 위임한다 이처럼 엔티티가 비즈니스 로직을 가지고 객체지향의 특성을 적극활용한것
  • JSP 개발할때 SQL 문을 이용해서 서비스 계층에 비즈니스 로직을 처리하는것을 트랜잭션 스크립트 패턴이라고 한다

4.주문 기능 테스트

  • 상품주문 성공
  • 상품주문할때 재고수량을 초과하면 안됨
  • 주문취소가 성공해야함OrderServiceTest (상품주문 성공)
@SpringBootTest
@Transactional
class OrderServiceTest {

    @Autowired
    EntityManager em;

    @Autowired OrderService orderService;
    @Autowired
    OrderRepository orderRepository;

    @Test
    public void 상품주문() throws Exception{
        //given
        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;

        //when
        Long orderId = orderService.order(member.getId(), book.getId(), orderCount);

        //then
        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(),"주문 수량만큼 재고가 줄어야 한다");

    }
}
  • assertEquals(원하는값,실제값,메세지)
  • orderService.order() 기능 테스트

OrderServiceTest (상품주문할때 재고수량을 초과하면 안됨)

@SpringBootTest
@Transactional
class OrderServiceTest {

    @Autowired
    EntityManager em;

    @Autowired OrderService orderService;
    @Autowired
    OrderRepository orderRepository;

    @Test
    public void 상품주문_재고수량초과() throws Exception{
        //given
        Member member = createMember();
        Item item = createBook("시골 JPA", 10000, 10);

        int orderCount = 11;

        //when
        NotEnoughStockException ex = assertThrows(NotEnoughStockException.class, () -> {
            orderService.order(member.getId(), item.getId(), orderCount);
        });

        //then
        assertEquals(ex.getMessage(), "need more stock");
    }

    private Book createBook(String name, int orderPrice, int stockQuantity) {
        Book book=new Book();
        book.setName(name);
        book.setPrice(orderPrice);
        book.setStockQuantity(stockQuantity);
        em.persist(book);
        return book;
    }

    private Member createMember() {
        Member member = new Member();
        member.setName("회원1");
        member.setAddress(new Address("서울","강가","123-123"));
        em.persist(member);
        return member;
    }
}
  • ctrl + alt + m 을 이용해서 매소드 분리
  • ctrl + alt + p 을 이용해서 매소드에 파라미터 값을 넣는다OrderServiceTest (상품취소)
@SpringBootTest
@Transactional
class OrderServiceTest {

    @Autowired
    EntityManager em;

    @Autowired OrderService orderService;
    @Autowired
    OrderRepository orderRepository;

    @Test
    public void 주문취소() throws Exception{
        //given
        Member member = createMember();
        Item item = createBook("시골 JPA", 10000, 10);

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

        //when
        orderService.cancelOrder(orderId);

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

5.주문 검색기능

  • 주문 검색 기능은 동적쿼리를 이용해서 만들어야한다OrderRepository
@Repository
@RequiredArgsConstructor
public class OrderRepository {

    public List<Order> findAllByCriteria(OrderSearch orderSearch) {
            CriteriaBuilder cb = em.getCriteriaBuilder();
            CriteriaQuery<Order> cq = cb.createQuery(Order.class);
            Root<Order> o = cq.from(Order.class);
            Join<Order, Member> m = o.join("member", JoinType.INNER); //회원과 조인
            List<Predicate> criteria = new ArrayList<>();
            //주문 상태 검색
            if (orderSearch.getOrderStatus() != null) {
                Predicate status = cb.equal(o.get("status"),
                        orderSearch.getOrderStatus());
                criteria.add(status);
            }
            //회원 이름 검색
            if (StringUtils.hasText(orderSearch.getMemberName())) {
                Predicate name =
                        cb.like(m.<String>get("name"), "%" +
                                orderSearch.getMemberName() + "%");
                criteria.add(name);
            }
            cq.where(cb.and(criteria.toArray(new Predicate[criteria.size()])));
            TypedQuery<Order> query = em.createQuery(cq).setMaxResults(1000); //최대1000건
            return query.getResultList();
        }

    @Getter @Setter
    public class OrderSearch {

    private String memberName; // 회원이름
    private OrderStatus orderStatus; //주문상태

}

}
728x90

'Back-End > Spring Data' 카테고리의 다른 글

8.변경감지 와 병합  (0) 2021.01.01
7.웹 계층 개발 1(회원,상품)  (0) 2021.01.01
5.상품도메인 개발  (0) 2020.12.30
4.회원도메인 개발  (0) 2020.12.30
3.엔티티클래스 개발2  (0) 2020.12.30