728x90
6.주문도메인개발
- 목차
- 1.주문 주문상품 엔티티 개발
- 2.주문 리포지토리 개발(Repository)
- 3.주문 서비스 개발(Service)
- 4.주문 기능 테스트
- 5.주문 검색기능
1.주문 주문상품 엔티티 개발
Order
@Entity
@Table(name = "orders")
@Getter @Setter
public class Order {
//생성 매세드 생성이 바뀔때 이것만 변경
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;
}
}
OrderItem
@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 getOrderPrice()*getCount();
}
}
- 생성할때 부터 createOrder() 를 호출 생성메서드에서 값을 셋팅해준다
- 비즈니스 주문취소 로직이 엔티티 안에있다 (도메인 주도설계)
- OrderItem 생성메서드를 만들어준다
- orderItem.cancel() 이동하면 OrderItem 에서 addStock 을 불러서 원래상태로 돌려준다
- 주문가격과 주문수량을 곱한 최종값을 totalPrice 반환하다
- OrderItem... 이런식으로 넘어오기보다는 DTO 를 만들어서 넘어온다!
2.주문 리포지토리 개발(Repository)
OrderRepository
@Repository
@RequiredArgsConstructor
public class OrderRepository {
private final EntityManager em;
public void save(Order order){
em.persist(order);
}
public Order findOne(Long id){
return em.find(Order.class,id);
}
//public List<Order> findAll(OrderSearch orderSearch){}
}
- //public List findAll(OrderSearch orderSearch){} 기능은 나중에!
3.주문 서비스 개발(Service)
- 상품 주문
- 주문 취소
- 주문 내역 조회OrderService (주문)
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
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 주문은 저장되기때문에 트랜잭션을 걸어준다
- 엔티티 조회할때 findOne 이용해서 찾는다
- 배송정보 생성할때 회원의 주소를 넣어준다
- 주문상품 생성할때 OrderItem 의 createOrderItem 호출 주문상품과,주문가격,수
- 주문 생성할때 Order 의 createOrder 를 호출 회원,배송지역,주문상품
- 주문 저장 save 한다음 order 의 id값 을 반환한다
- orderRepository.save(order); -> CascadeType.ALL 이기때문에 OrderItem 과 Delivery 도 같이 persist 된다
- OrderItem 과 Delivery 가 다른데에서도 쓰일때 별도의 리포지토리를 만들어서 persist 해줘야한다
- 다른데에서 참조하지 않을때 CascadeType.ALL 쓴다!
이번 Order 는 Order 만 OrderItem 과 Delivery 를 사용하기때문에 CascadeType.ALL 쓴것이다!
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OrderService {
//주문상품 생성
OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count);
//OrderItem orderItem1=new OrderItem();
protected OrderItem() {
}
}
- OrderItem orderItem1=new OrderItem(); 을 막아주기위해서 OrderItem 에서 protected 생성자를 만들어준다
- @NoArgsConstructor(access = AccessLevel.PROTECTED) 롬복을 이용해도 된다
- 코드를 항상 제약하는 형식으로 코딩한다
OrderService (주문취소)
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final MemberRepository memberRepository;
private final ItemRepository itemRepository;
//취소
@Transactional
public void cancelOrder(Long orderId){
//주문 엔티티 조회
Order order=orderRepository.findOne(orderId);
//주문취소
order.cancel();
}
}
- order -> cancel -> OrderStatus.CANCEL -> OrderItem -> cancel -> addStock
- JPA 변경을 감지해서 (더티체킹) 데이터베이스에 Update 쿼리가 나간다
- 도메인 모델 패턴 : 서비스 계층은 단순히 엔티티에 필요한 요청을 위임한다 이처럼 엔티티가 비즈니스 로직을 가지고 객체지향의 특성을 적극활용한것
- JSP 개발할때 SQL 문을 이용해서 서비스 계층에 비즈니스 로직을 처리하는것을 트랜잭션 스크립트 패턴이라고 한다
4.주문 기능 테스트
- 상품주문 성공
- 상품주문할때 재고수량을 초과하면 안됨
- 주문취소가 성공해야함OrderServiceTest (상품주문 성공)
@SpringBootTest
@Transactional
class OrderServiceTest {
@Autowired
EntityManager em;
@Autowired OrderService orderService;
@Autowired
OrderRepository orderRepository;
@Test
public void 상품주문() throws Exception{
//given
Member member = new Member();
member.setName("회원1");
member.setAddress(new Address("서울","강가","123-123"));
em.persist(member);
Book book=new Book();
book.setName("시골 JPA");
book.setPrice(10000);
book.setStockQuantity(10);
em.persist(book);
int orderCount=2;
//when
Long orderId = orderService.order(member.getId(), book.getId(), orderCount);
//then
Order getOrder = orderRepository.findOne(orderId);
assertEquals(OrderStatus.ORDER,getOrder.getStatus(),"상품주문시 상태는 ORDER");
assertEquals(1,getOrder.getOrderItems().size(),"주문한 상품 종류 수가 정확해야한다");
assertEquals(10000*orderCount,getOrder.getTotalPrice(),"주문 가격은 가격 * 수량이다");
assertEquals(8,book.getStockQuantity(),"주문 수량만큼 재고가 줄어야 한다");
}
}
- assertEquals(원하는값,실제값,메세지)
- orderService.order() 기능 테스트
OrderServiceTest (상품주문할때 재고수량을 초과하면 안됨)
@SpringBootTest
@Transactional
class OrderServiceTest {
@Autowired
EntityManager em;
@Autowired OrderService orderService;
@Autowired
OrderRepository orderRepository;
@Test
public void 상품주문_재고수량초과() throws Exception{
//given
Member member = createMember();
Item item = createBook("시골 JPA", 10000, 10);
int orderCount = 11;
//when
NotEnoughStockException ex = assertThrows(NotEnoughStockException.class, () -> {
orderService.order(member.getId(), item.getId(), orderCount);
});
//then
assertEquals(ex.getMessage(), "need more stock");
}
private Book createBook(String name, int orderPrice, int stockQuantity) {
Book book=new Book();
book.setName(name);
book.setPrice(orderPrice);
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;
}
}
- ctrl + alt + m 을 이용해서 매소드 분리
- ctrl + alt + p 을 이용해서 매소드에 파라미터 값을 넣는다OrderServiceTest (상품취소)
@SpringBootTest
@Transactional
class OrderServiceTest {
@Autowired
EntityManager em;
@Autowired OrderService orderService;
@Autowired
OrderRepository orderRepository;
@Test
public void 주문취소() throws Exception{
//given
Member member = createMember();
Item 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(OrderStatus.CANCEL,getOrder.getStatus(),"주문 취소시 상태는 CANCEL 이다");
assertEquals(10L,item.getStockQuantity(),"주문이 취소된 상품은 그만큼 재고가 증가해야한다");
}
}
5.주문 검색기능
- 주문 검색 기능은 동적쿼리를 이용해서 만들어야한다OrderRepository
@Repository
@RequiredArgsConstructor
public class OrderRepository {
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();
}
@Getter @Setter
public class OrderSearch {
private String memberName; // 회원이름
private OrderStatus orderStatus; //주문상태
}
}
- 어렵다.. 나중에 알아보자.. 왠만하면 Querydsl 이용하자참고자료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/dashboard (웹 애플리케이션개발)
728x90
'Back-End > Spring Data' 카테고리의 다른 글
8.변경감지 와 병합 (0) | 2021.01.01 |
---|---|
7.웹 계층 개발 1(회원,상품) (0) | 2021.01.01 |
5.상품도메인 개발 (0) | 2020.12.30 |
4.회원도메인 개발 (0) | 2020.12.30 |
3.엔티티클래스 개발2 (0) | 2020.12.30 |