솜이의 데브로그

[Spring Boot] 주문 도메인 개발(2) 본문

dev/Spring Boot

[Spring Boot] 주문 도메인 개발(2)

somsoming 2021. 12. 3. 01:42

Reference : Inflearn 실전 스프링부트와 JPA 활용 (김영한님 강의)


주문 기능 테스트

 

  • 상품 주문 성공
  • 상품 주문 시 재고수량을 초과하면 안된다
  • 주문 취소가 성공해야함

위의 조건들을 만족하는지 테스트해보자.

 

OrderServiceTest.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.exceptioin.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.aspectj.bridge.MessageUtil.fail;
import static org.junit.Assert.assertEquals;

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class OrderServiceTest {

    @Autowired EntityManager em;
    @Autowired OrderService orderService;
    @Autowired OrderRepository orderRepository;

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

    @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 getOrder = orderRepository.findOne(orderId);

        assertEquals("주문 취소시 상태는 CANCEL이다.", OrderStatus.CANCEL, getOrder.getStatus());
        assertEquals("주문이 취소된 상품은 그만큼 재고가 증가해야 한다.", 10, item.getStockQuantity());

    }
    
    @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("재고 수량 부족 예외가 발생해야 한다.");

    }

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

}

 

 

Assert는 인수를 검증하고 조건에 맞지 않는지 확인

→ assertEquals, fail 등

 

주문 초과하는 수량으로 테스트해보면 로직에서 예외가 발생.

 

order entity에 대해서 비즈니스 로직을 작성해버리는 것도 가능.

단위 테스트로 작성하는 것이 중요.

 

 

주문 검색 기능 개발

JPA에서 동적 쿼리를 어떻게 해결해야 하는가?

 

repository/OrderSearch.java

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;  //주문 상태[ORDER, CANCEL]
}

 

repository/OrderRepository.java

 

동적 쿼리 생성

findAll 함수 작성시 세가지 방법

 

(1) JPQL로 처리

(생략..) 안씀

 

(2) JPA Criteria로 처리

/**
     * JPA Criteria
     */
    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<Object, Object> 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);
        return query.getResultList();

    }

이것도 잘 안씀. 실무에서 사용하기에 너무 복잡하다.

 

(3) Querydsl 로 처리

public List<Order> findAll(OrderSearch orderSearch){
	QOrder order = QOrder.order;
    QMember member = QMember.member;
    
    return query
    	.select(order)
        .from(order)
        .join(order.member, member)
        .where(statusEq(orderSearch.getOrderStatus()),
        	nameLike(orderSearch.getMemberName()))
        .limit(1000)
        .fetch();
}

privte BooleanExpression statusEq(OrderStatus statusCond) {
	if(statusCond == null) {
    	return null;
    }
    return order.status.eq(statusCond);
}

private BooleanExpression nameLike(String nameCond) {
	if(!StringUtils.hasText(nameCond)) {
    	return null;
    }
    return member.name.like(nameCond);
}

 

 

실무에서는 Spring Boot, Springdata JPA, querydsl 을 잘 알아둬야한다~~

 

 

더 공부해봐야할것

@Autowired 어노테이션

EntityManager 사용법.. em.persist 해서 어디에 넣어주는건데..

→ Entity 내부에 영속성 컨텍스트 (Persistence Context) 를 두어서 엔티티를 관리하는 것!! 즉, 비휘발성으로 관리하여 엔티티를 영구적으로 저장하는 환경.

 

 

ㅋㅋㅋ 공부를 하면 할수록 모르는게 이렇게나 많고.. 공부해야할건 더욱 더 많다는것을 느낀다. 눈물..