Spring BootとJPA 1-TIL(4)を使用


[参考講座]実戦!Spring BootとJPAによる1-Webアプリケーションの開発

💡 受注ドメインの開発


実装機能

  • 商品注文
  • クエリ
  • 受注履歴
  • 注文キャンセル
  • ▼▼注文、注文商品本体の開発


    受注エンティティの開発

    @Entity
    @Table(name = "orders")
    @Getter @Setter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    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(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
        @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 = 0;
            for (OrderItem orderItem : orderItems) {
                totalPrice += orderItem.getTotalPrice();
            }
            return totalPrice;
        }
    }
    

    機能の説明

  • 作成方法(createOrder():受注エンティティを作成します.受注会員、配送情報、受注商品の情報を受け取り、実際の受注エンティティを生成します.
  • キャンセルオーダー(cancel():キャンセルオーダーに使用します.受注ステータスをキャンセルに変更し、受注商品に受注のキャンセルを通知します.
  • 全発注価格の照会:発注時に使用される全発注価格の照会.全受注価格を知るには、各受注商品の価格を知る必要があります.論理的に見ると、関連注文品の価格を調べて、もう一つの価格を返します.
  • 受注エンティティの開発

    @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():受注商品、価格、数量情報を使用して受注商品エンティティを生成します.
  • キャンセル():getItem()です.addStock(count)を呼び出し、キャンセルされた注文数に応じて商品在庫を増やします.
  • 受注価格照会(getTotalPrice):受注価格に数量を乗じた値を返します.
  • ▼▼▼受注記録庫の開発


    オーダーレコードコード

    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);
        }
    }

    ▼▼▼開発発注サービス


    注文サービスコード

    @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(Order)
    }
    受注サービスは、受注エンティティと受注商品エンティティのビジネスロジックを使用して、受注、受注のキャンセル、受注履歴の取得機能を提供します.
  • 受注(order()):受注の会員ID、商品ID、受注数量情報を受信し、実際の受注エンティティを生成して保存します.
  • キャンセルオーダー(キャンセルオーダー():受注識別子を受信し、受注エンティティを問合せ、受注エンティティに受注のキャンセルを要求します.
  • 受注検索(FindOrders):OrderSearchという検索条件を使用して受注エンティティを検索します.詳細については、次の受注検索機能を参照してください.
  • 参照)
    注文サービスの注文とキャンセルの方法から見ると、ほとんどのビジネスロジックはエンティティにあります.サービス・レイヤは、エンティティの委任に必要なリクエストのみを担当します.このように、エンティティがビジネスロジックを有し、オブジェクト指向の特性を積極的に利用することをドメインモデルモデルモデルと呼ぶ.
    逆に、エンティティにはビジネスロジックがほとんどなく、サービス・レイヤでほとんどのビジネスロジックを処理することをトランザクション・スクリプト・モードと呼びます.

    受注機能のテスト


    テスト要件
  • の商品を正常に注文しなければなりません.
  • 商品の注文は在庫数量を超えてはならない.
  • の注文を正常にキャンセルする必要があります.
  • @RunWith(SpringRunner.class)
    @SpringBootTest
    @Transactional
    public 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;
    
            //when
            Long orderId = orderService.order(member.getId(), item.getId(), orderCount);
    
            //then
            Order getOrder = orderRepository.findOne(orderId);
    
            assertEquals("상품 주문시 상태는 ORDER", OrderStatus.ORDER, getOrder.getStatus());
            assertEquals("주문한 상품 종류 수가 정확해야 한다.", 1, getOrder.getOrderItems().size());
            assertEquals("주문 가격은 가격 * 수량이다.", 10000 * orderCount, getOrder.getTotalPrice());
            assertEquals("주문 수량만큼 재고가 줄어야 한다.", 8, item.getStockQuantity());
        }
    
        private Book createBook(String name, int price, int stockQuantity) {
            Book book = new Book();
            book.setName(name);
            book.setPrice(price);
            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;
        }
    
        @Test(expected = NotEnoughStockException.class)
        public void 상품주문_재고수량초과() throws Exception {
            //given
            Member member = createMember();
            Item item = createBook("시골 JPA", 10000, 10);
    
            int orderCount = 11;
    
            //when
            orderService.order(member.getId(), item.getId(), orderCount);
    
            //then
            fail("재고 수량 부족 예외가 발생해야 한다.");
        }
    
        @Test
        public void 주문취소() throws Exception {
            //given
            Member member = createMember();
            Book 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("주문 취소시 상태는 CANCEL이다.", OrderStatus.CANCEL, getOrder.getStatus());
            assertEquals("주문이 취소된 상품은 그만큼 재고가 증가해야 한다.", 10, item.getStockQuantity());
        }
    }

    Web層を開発してみましょう