package jpabook.jpashop.domain;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "orders")
@Getter
@Setter
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) {
this.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()
- 주문 취소시 사용
- 주문 상태를 취소로 변경
- 주문상품에 주문 취소 처리
- 이미 배송을 완료한 상품이면 주문을 취소하지 못하도록 예외 발생
- getTotalPrice()
- 주문 시 사용한 전체 주문 가격 조회
package jpabook.jpashop.domain;
import jpabook.jpashop.domain.item.Item;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@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 getCount() * getOrderPrice();
}
}
기능 설명
- createOrderItem()
- 주문 상품, 가격, 수량 정보를 사용해서 주문상품 엔티티를 생성
- item.removeStock(count) 호출
- 주문한 수량만큼 상품의 재고 감소
- cancel()
- getItem().addStock(count) 호출
- 취소한 주문 수량만큼 상품의 재고를 증가
- getTotalPrice()
- 주문 가격에 수량을 곱한 값을 반환
package jpabook.jpashop.repository;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.domain.Order;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class OrderRepository {
private final EntityManager em;
public void save(Order order) {
em.persist(order);
}
public Order findOne(Long orderId) {
return em.find(Order.class, orderId);
}
}
package jpabook.jpashop.service;
import jpabook.jpashop.domain.Delivery;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.domain.Order;
import jpabook.jpashop.domain.OrderItem;
import jpabook.jpashop.domain.item.Item;
import jpabook.jpashop.repository.ItemRepository;
import jpabook.jpashop.repository.MemberRepository;
import jpabook.jpashop.repository.OrderRepository;
import jpabook.jpashop.repository.OrderSearch;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
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();
}
}
```java
package jpabook.jpashop.service;
import jpabook.jpashop.domain.Address;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.domain.Order;
import jpabook.jpashop.domain.OrderStatus;
import jpabook.jpashop.domain.item.Book;
import jpabook.jpashop.domain.item.Item;
import jpabook.jpashop.exception.NotEnoughStockException;
import jpabook.jpashop.repository.OrderRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@RunWith(SpringRunner.class)
@Transactional
@SpringBootTest
public class OrderServiceTest {
@Autowired
EntityManager em;
@Autowired
OrderService orderService;
@Autowired
OrderRepository orderRepository;
@Test
public void 상품주문() throws Exception {
//given
Member member = createMember();
Book book = createBook("책이름1", 10000, 100);
int orderCount = 3;
//when
Long orderId = orderService.order(member.getId(), book.getId(), orderCount);
//then
Order findOrder = orderRepository.findOne(orderId);
assertEquals("상품 주문 시 상태는 ORDER여야 함", OrderStatus.ORDER, findOrder.getStatus());
assertEquals("주문항 상품 종류의 수가 일치해야한다.", 1, findOrder.getOrderItems().size());
assertEquals("주문 가격은 가격 * 수량 이다.", 30000, findOrder.getTotalPrice());
assertEquals("주문 수량 만큼 재고가 줄어야 한다.", 97, book.getStockQuantity());
}
@Test(expected = NotEnoughStockException.class)
public void 상품주문_재고수량초과() throws Exception {
//given
Member member = createMember();
Item item = createBook("책이름1", 10000, 100);
int orderCount = 101;
//when
orderService.order(member.getId(), item.getId(), orderCount);
//then
fail("재고 수량 예외가 발생해야 한다.");
}
@Test
public void 주문취소() throws Exception {
//given
Member member = createMember();
Item item = createBook("책이름1", 10000, 100);
int orderCount = 30;
Long orderId = orderService.order(member.getId(), item.getId(), orderCount);
//when
orderService.cancelOrder(orderId);
//then
Order findOrder = orderRepository.findOne(orderId);
assertEquals("주문 취소 시 상태는 CANCEL이다.", OrderStatus.CANCEL, findOrder.getStatus());
assertEquals("주문이 취소되면 재고가 원상복구 되어야한다.", 100, item.getStockQuantity());
}
private Member createMember() {
Member member = new Member();
member.setName("회원1");
member.setAddress(new Address("서울", "길거리", "15243"));
em.persist(member);
return member;
}
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;
}
}
```
> **Given**
>
> - 테스트를 위한 회원과 상품을 생성
>
> **When**
>
> - 실제 상품을 주문
>
> **Then**
>
> - 주문 가격이 올바른지, 주문 후 재고 수량이 정확히 줄었는지 검증
package jpabook.jpashop.repository;
import jpabook.jpashop.domain.OrderStatus;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class OrderSearch {
private String memberName; // 회원 이름
private OrderStatus orderStatus; // 주문 상태
}
public List<Order> findAll(OrderSearch orderSearch) {
//... 검색 로직
}
📌 **findAll(OrderSearch orderSearch)**
public List<Order> findAllByString(OrderSearch orderSearch) {
//language=JPAQL
String jpql = "select o From Order o join o.member m";
boolean isFirstCondition = true;
//주문 상태 검색
if (orderSearch.getOrderStatus() != null) {
if (isFirstCondition) {
jpql += " where";
isFirstCondition = false;
} else {
jpql += " and";
}
jpql += " o.status = :status";
}
//회원 이름 검색
if (StringUtils.hasText(orderSearch.getMemberName())) {
if (isFirstCondition) {
jpql += " where";
isFirstCondition = false;
} else {
jpql += " and";
}
jpql += " m.name like :name";
}
TypedQuery<Order> query = em.createQuery(jpql, Order.class)
.setMaxResults(1000); //최대 1000건
if (orderSearch.getOrderStatus() != null) {
query = query.setParameter("status", orderSearch.getOrderStatus());
}
if (StringUtils.hasText(orderSearch.getMemberName())) {
query = query.setParameter("name", orderSearch.getMemberName());
}
return query.getResultList();
}
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();
}