주문, 주문상품 엔티티 개발
주문 엔티티 (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) 로 막아준다.
이 게시물은 '실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발' 강의를 수강하고 정리한 내용임을 밝힙니다.
실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 인프런 | 강의
실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다., 본
www.inflearn.com