Per ardua ad astra !
I'm On My Way
Per ardua ad astra !
전체 방문자
오늘
어제
  • 분류 전체보기 (126)
    • Algorithm (50)
      • 백준 (30)
      • SWEA (3)
      • JUNGOL (3)
      • Programmers (5)
      • LeetCode (2)
    • 안드로이드 개발 (6)
      • Java로 개발 (0)
      • Kotlin으로 개발 (3)
    • Spring (41)
      • Spring기본 (17)
      • JPA기본 (15)
      • JPA활용 SpringBoot 기본 (9)
      • API 개발 기본 (0)
    • 네트워크 (3)
    • 운영체제 (0)
    • Life (3)
      • 책 (0)
      • 자기계발 (1)
      • 일상 (2)
    • 노마드코더 (3)
      • python으로 웹 스크래퍼 만들기 (3)
    • 프로그래밍 언어 (17)
      • Java 기본 (2)
      • 코틀린 기본 (15)

블로그 메뉴

  • 홈
  • 방명록

인기 글

hELLO · Designed By 정상우.
Per ardua ad astra !

I'm On My Way

Spring/JPA활용 SpringBoot 기본

주문 도메인 개발

2021. 8. 20. 16:51

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


주문 엔티티 (Order)

package jpabook.jpashop.domain;

import lombok.Getter;
import lombok.Setter;
import org.aspectj.weaver.ast.Or;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

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

    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 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 cancelOrder(Order order){
        if(order.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.getTotalOrderPrice();
        }
        return totalPrice;
    }
}



주문 상품 엔티티(OrderItem)

package jpabook.jpashop.domain;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Getter
@Setter
@Table(name = "order_item")
public class OrderItem {

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

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "item_id")
    private Item item;

    private int orderPrice; // 주문 가격
    private int count; // 주문 수량

    //== 생성 메서드 ==//
    public OrderItem createOrderItem(Item item, int orderPrice, int count) {
        OrderItem orderItem = new OrderItem();
        orderItem.setItem(item);
        orderItem.setCount(count);
        orderItem.setOrderPrice(orderPrice);

        item.removeStock(count);
        return orderItem;
    }

    //== 비즈니스 로직 ==//
    //주문 취소
    public void cancel() {
        item.addStock(count);
    }

    public int getTotalOrderPrice() {
        return getOrderPrice() * getCount();
    }
}


Order 부터

  • 생성 메소드 (Order를 생성하는 createOrder() 메소드를 만들어 준다)
     -> 마지막 매개변수의 ...(점점점) 문법은 가변인자로, 몇개의 객체변수가 오든 상관없고 
         iter를 활용해 반복문으로 쉽게 접근할 수 있다.
     -> order를 기준으로 OrderItem이 추가되기 때문에 orderItem하나하나를 addOrderItem()을 이용하여 추가해준다.
     -> 나머지는 주문한 것에 맞게 값을 넣어준다.

  • 비즈니스 로직 (cancelOrder() 주문 취소)
     -> 배송 중인상품은 주문 취소가 안되도록 Exception 발생
     -> 그게 아니라면 취소 상태로 만들고 orderItem을 통해 다시 상품 개수를 늘릴 수 있도록 함

  • 조회 로직 (전체 주문가격 조회)
    -> 코드보고 이해하면 될듯하다.


OrderItem

 생성 메소드를 똑같이 만듬
  -> 주의할점은 상품(Item)에서 재고를 줄여줘야 된다는 것이다.
  -> orderPirce와 item.getPrice()를 구분하는 것은 orderPrice가 할인가를 반영할 수도 있기 때문 

 

 

도메인 모델 패턴 (createOrder, createOrderItem, orderCancel 등)

  • 비즈니스 로직 대부분이 엔티티에 있다. 서비스 계층은 단순히 엔티티에 필요한 요청을 위임하는 역할을 한다.
    이와 같은 모델을 도메인 모델 패턴이라고 한다.  
     
  • <-> 반대로 서비스 계층에서 대부분의 비즈니스 로직을 처리하는것을 트랜잭션 스크립트 패턴이라고 한다.

  • 엔티티 단에서 이렇게 데이터만 바꿔줘도 JPA가 알아서 바뀐 변경포인터들(dirty checking)을 통해서 업데이트 쿼리를 자동으로 날릴 수 있다.
         
  • 그냥 SQL을 사용했다면 서비스 단에서 일일히 변경해야 겠지만 안그래도 된다는 점이 장점이다.


주문 레포지터리 개발 (OrderRepository)

package jpabook.jpashop.repository;

import jpabook.jpashop.domain.Order;
import jpabook.jpashop.domain.OrderStatus;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;
import java.util.List;

@Repository
@RequiredArgsConstructor
public class OrderRepository {

    private final EntityManager em;

    public Long save(Order order) {
        em.persist(order);
        return order.getId();
    }

    public Order findOne(Long orderId) {
        return em.find(Order.class, orderId);
    }

    public List<Order> findAll() {
        return em.createQuery("select o from Order o", Order.class).getResultList();
    }
	
     public List<Order> findAll(OrderSearch orderSearch) {
        return em.createQuery("Select o FROM Order o JOIN o.member m ON m.name = :name " +
                "WHERE o.status = :status", Order.class
        )
                .setParameter("name", orderSearch.getMemberName())
                .setParameter("status", orderSearch.getOrderStatus())
                .setMaxResults(1000)
                .getResultList();
    }
    
//    public List<Order> findByName(String name) {
//        return em.createQuery("select o from Order o join fetch Member m where m.name = :name")
//                .setParameter("name", name)
//                .getResultList();
//    }
//
//    public List<Order> findByNameAndStatus(String name, OrderStatus orderStatus) {
//        return em.createQuery("select o from Order o join fetch Member m where m.name = :name and o.status = :status")
//                .setParameter("name", name)
//                .setParameter("status", orderStatus)
//                .getResultList();
//    }
}


주문정보 조회 메서드는 다음과 같다. 
- 주문 하나 조회
- 주문 전체 조회
- 회원 이름으로 주문 조회 
- 회원 이름과 주문상태로 조회 

 

아래 두개의 조회 방법은 동적 쿼리를 요구함으로 나중에 Query Dsl을 배워서 쉽게 해결할 수 있도록 해야겠다.

 


주문 서비스개발(OrderService)

package jpabook.jpashop.service;

import jpabook.jpashop.domain.*;
import jpabook.jpashop.repository.ItemRepository;
import jpabook.jpashop.repository.MemberRepository;
import jpabook.jpashop.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional
public class OrderService {

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

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

    public void cancelOrder(Long orderId){ // CANCEL 버튼을 누르면 id만 넘어온다.
        // 주문 엔티티 조회
        Order order = orderRepository.findOne(orderId);
        // 주문 취소
        order.cancelOrder();
    }

    @Transactional(readOnly = true)
    public List<Order> findOrders(OrderSearch orderSearch){
        return orderRepository.findAll(orderSearch);
    }
}


order() 메소드에서 createOrderItem()과 createOrder로 order와 orderItem을 각각 생성한다. 

이렇게 생성하지 않고 생성자를 통해서 만드는 경우 무분별하게 생성할 수 있어서, 즉 생성로직이 분산되서 유지보수가 힘들다. 무분별한 생성자 사용을 막기 위해서

엔티티단에서 
@NoArgsConstructor(access = AccessType.PROTECTED) 로 막아준다.

 

SpringBoot-Basic-main.zip
0.08MB

 

이 게시물은 '실전! 스프링 부트와 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/

 

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

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

www.inflearn.com

 

 

    'Spring/JPA활용 SpringBoot 기본' 카테고리의 다른 글
    • MVC 계층 개발 2. 상품 개발, 변경 감지와 병합
    • MVC 계층 개발 1. 홈화면, 회원등록 및 목록 조회 (Spring-boot-starter-validation 사용) (thymeleaf 배우기) (BindingResult, @Valid)
    • 상품 도메인 개발, @Transactional에 대한 궁금증 해소
    • 회원 도메인 개발(레포지터리, 서비스, 테스트) && 의존주입 생략 과정, JUnit5를 이용한 테스트, 내부 DB로 전환
    Per ardua ad astra !
    Per ardua ad astra !
    개발자 지망생이며 열심히 공부하고 기억하기 위한 블로그입니다.

    티스토리툴바