참고 : 인프런 [ 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 김영한 ]
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();
}
@NoArgsConstructor(access = AccessLevel.PROTECTED) : 롬복
생성 메서드를 사용하지 않고 new OrderItem(); 처럼 객체를 생성한후 Setter을 사용하여 객체를 각각 세팅하는 방법을 사용하게 되면, 추후 유지보수가 어려워진다.
이를 해결하기 위해 @NoArgsConstructor(access = AccessLevel.PROTECTED) 어노테이션을 사용하여,
객체를 직접 생성하는 것을 방지한다.
/**
* 주문 취소
*/
@Transactional
public void cancelOrder(Long orderId){
//엔티티 조회
Order order = orderRepository.findOne(orderId);
//주문 취소
order.cancle();
}
Dirty Checking 이라는 변경 포인트 감지가 일어나면서 데이트베이스에 업데이트 쿼리를 자동으로 날리게 된다.참고: 주문 서비스의 주문과 주문 취소 메서드를 보면 비즈니스 로직 대부분이 엔티티에 있다. 서비스 계층은 단순 히 엔티티에 필요한 요청을 위임하는 역할을 한다.
- 이처럼 엔티티가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 것을 도메인 모델 패턴(http://martinfowler.com/eaaCatalog/domainModel.html)이라 한 다.
- 반대로 엔티티에는 비즈니스 로직이 거의 없고 서비스 계층에서 대부분의 비즈니스 로직을 처리하는 것을 트 랜잭션 스크립트 패턴(http://martinfowler.com/eaaCatalog/transactionScript.html)이라 한다.
...
@Test
public void 상품주문() throws Exception {
//given
Member member = createMember();
Book book = createBook("시골 JPA", 10000, 10);
int orderCount=2;
//when
Long orderId = orderService.order(member.getId(), book.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,book.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;
}
...
원활한 테스트를 위해 createMember(), createBook()은 메서드로 정의한다.
...
@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 getOrderId = orderRepository.findOne(orderId);
assertEquals("주문 취소시 상태는 CANCEL 이다.", OrderStatus.CANCLE, getOrderId.getStatus());
assertEquals("주문이 취소된 상품은 그만큼 재고가 증가되야 한다.", 10, item.getStockQuantity());
}

통합테스트 결과 모두 성공한 것을 확인하였다!