주문 도메인 개발

2023. 5. 18. 05:08카테고리 없음

구현기능

  • 상품주문
  • 주문내역조회
  • 주문취소
  • 순서*
  • 주문엔티티, 주문상품엔티티개발
  • 주문리포지토리 개발
  • 주문서비스개발
  • 주문검색기능개발
  • 주문기능테스트

주문 엔티티 코드

package jpabook.jpashop.domain;

import jakarta.persistence.*;

import lombok.Getter;
import lombok.Setter;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import static jakarta.persistence.FetchType.*;

@Entity
@Table(name = "orders")
@Getter
@Setter
public class Order {
    @Id
    @GeneratedValue
    @Column(name = "order_id")
    private Long id;

    @ManyToOne(fetch = LAZY)// 다대일 관계
    @JoinColumn(name = "member_id")
    private Member member;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    //cascade 코드의 사용으로 굳이 persist를 적지 않아도 알아서 persist가 된다.
    private List<OrderItem> orderItems = new ArrayList<>();

    @OneToOne(cascade = CascadeType.ALL, fetch = LAZY)
    // 하나의 주문은 하나의 배송만 받아야함. 그리고 하나의 배송은 하나의 주문만 받아야함.
    @JoinColumn(name = "delivery_id")
    private Delivery delivery; //배송정보

    //order_date 로 바뀜 카멜 케이스로 인하여.
    private LocalDateTime orderDate; //주문시간

    @Enumerated(EnumType.STRING)
    private OrderStatus status; //주문상태 [ORDER, CANCEL]

    //==연관관계 메서드==//
    public void setMember(Member member) {
        this.member = member; // 여기서 this는 현재 클래스를 뜻함.(order)
        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) {
        // 하나하나 set을 하는게 아니라 여기 creatOrder만 실행해도 다 나오게끔 여기에 다 넣음.
        //주문엔티티를 생성할때 사용. 주문회원, 배송정보, 주문상품의 정보를받아서 실제 주문엔티티를 생성.
        // ...을 하면 여러개를 넘기게 할 수있음?(질문)
        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() { //여기서 취소하면 addstock을 해서 재고가 다시 늘어남.
        if (delivery.getStatus() == DeliveryStatus.COMP) { // 배송이 이미 끝남
            throw new IllegalStateException("이미 배송완료된 상품은 취소가 불가능합니다.");
            //예외 터트리기.
        }
        this.setStatus(OrderStatus.CANCEL); //나의 상태를 CANCEL로 바꿈
        for (OrderItem orderItem : orderItems) {
            orderItem.cancel();
            // 주문한거 하나하나 다 취소(다대일 관계이기 때문에 하나하나 다 삭제)
        }
    }


    //==조회 로직==//

    /**
     * 전체 주문 가격 조회
     */
    public int getTotalPrice() { // 주문 수량 * 주문가격
        int totalPrice = 0; // 0으로 초기화
        for (OrderItem orderItem : orderItems) { // orderItems를 루프를 돌면서
            totalPrice += orderItem.getTotalPrice(); // orderItem에 있는 TotalPrice를 가져옴
        }
        return totalPrice;
    }
}

주문상품 엔티티 코드

package jpabook.jpashop.domain;

import jakarta.persistence.*;
import jpabook.jpashop.domain.item.Item;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.cache.spi.support.AbstractReadWriteAccess;

import static jakarta.persistence.FetchType.LAZY;

@Entity
@Table(name = "order_item")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 값을 따로 주려고 할때 못하도록 알아차리게 해주는 코드?(질문)
public class OrderItem {


    @Id
    @GeneratedValue
    @Column(name = "order_item_id") //(FK)
    private Long id;


    @ManyToOne(fetch = LAZY)// 다대일
    @JoinColumn(name = "item_id") //(FK) item을 참조할거니까 FK
    private Item item; //주문 상품


    @ManyToOne(fetch = LAZY) //다대일
    @JoinColumn(name = "order_id")
    // 하나의 Order가 여러개의 Orderitem을 가질 수 있지만, 반대로 아이템은 하나의 오더만 가능함.
    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);
        //Item이 가지고 있는 것이기 때문에 재고수량을 다시 롬복(늘려줌)해줌.
    }
    //==조희 로직==//

    /**
     * 주문 상품 전체 가격 조회
     */
    public int getTotalPrice() { //totalPrice 계산
        return getOrderPrice() * getCount();
    }
}

주문 레퍼지토리

package jpabook.jpashop.repository;

import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.*;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.domain.Order;
import jpabook.jpashop.domain.OrderSearch;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

@Repository
@RequiredArgsConstructor
public class OrderRepository {
    private final EntityManager em; // 주입

    public void save(Order 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.*;
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.aspectj.weaver.ast.Or;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService {
    //밑에서 조회할때 사용하려고 설정해놓은것.
    private final MemberRepository memberRepository;
    private final OrderRepository orderRepository;
    private final ItemRepository itemRepository;
    /** 주문 */
    @Transactional
    public Long order(Long memberId, Long itemId, int count) {
        // 상품을 선택할때 상품 Id만 넘어오는 것이기 때문에 repository가 많이 필요함.

    //엔티티 조회
        Member member = memberRepository.findOne(memberId);  // 멤버 찾기
        Item item = itemRepository.findOne(itemId); // 아이템id가져오기

    //배송정보 생성
        Delivery delivery = new Delivery(); //생성자 만들고
        delivery.setAddress(member.getAddres()); //생성자에 address 설정하고
        delivery.setStatus(DeliveryStatus.READY); // 생성자의 배달상태를 준비로 설정.

    //주문상품 생성
        OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(),count);
        // 라이프 사이클? 왔?(질문)
        // 다른곳에서 참조할수없는 private 오너라면 유용하게 사용함.
        // 생성 메서드 createOrderItem 사용

    //주문 생성
        Order order = Order.createOrder(member,delivery,orderItem);
        //생성 메서드 createOrder

    //주문 저장
        orderRepository.save(order);
        //(질문)
        // cascade 덕분에 이 한줄만 써도 delivery랑 orderItem이 persist가 자동으로 됨.

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

}
  • 주문서비스는주문 엔티티와주문상품엔티티의비즈니스로직을 활용해서주문, 주문취소, 주문 내역 검색기능을제공한다.

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

 

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

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/dashboard