주문 도메인 개발
2023. 5. 18. 05:08ㆍ카테고리 없음
구현기능
- 상품주문
- 주문내역조회
- 주문취소
- 순서*
- 주문엔티티, 주문상품엔티티개발
- 주문리포지토리 개발
- 주문서비스개발
- 주문검색기능개발
- 주문기능테스트
주문 엔티티 코드
package jpabook.jpashop.domain;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import static jakarta.persistence.FetchType.*;
@Entity
@Table(name = "orders")
@Getter
@Setter
public class Order {
@Id
@GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne(fetch = LAZY)// 다대일 관계
@JoinColumn(name = "member_id")
private Member member;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
//cascade 코드의 사용으로 굳이 persist를 적지 않아도 알아서 persist가 된다.
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne(cascade = CascadeType.ALL, fetch = LAZY)
// 하나의 주문은 하나의 배송만 받아야함. 그리고 하나의 배송은 하나의 주문만 받아야함.
@JoinColumn(name = "delivery_id")
private Delivery delivery; //배송정보
//order_date 로 바뀜 카멜 케이스로 인하여.
private LocalDateTime orderDate; //주문시간
@Enumerated(EnumType.STRING)
private OrderStatus status; //주문상태 [ORDER, CANCEL]
//==연관관계 메서드==//
public void setMember(Member member) {
this.member = member; // 여기서 this는 현재 클래스를 뜻함.(order)
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 static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems) {
// 하나하나 set을 하는게 아니라 여기 creatOrder만 실행해도 다 나오게끔 여기에 다 넣음.
//주문엔티티를 생성할때 사용. 주문회원, 배송정보, 주문상품의 정보를받아서 실제 주문엔티티를 생성.
// ...을 하면 여러개를 넘기게 할 수있음?(질문)
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() { //여기서 취소하면 addstock을 해서 재고가 다시 늘어남.
if (delivery.getStatus() == DeliveryStatus.COMP) { // 배송이 이미 끝남
throw new IllegalStateException("이미 배송완료된 상품은 취소가 불가능합니다.");
//예외 터트리기.
}
this.setStatus(OrderStatus.CANCEL); //나의 상태를 CANCEL로 바꿈
for (OrderItem orderItem : orderItems) {
orderItem.cancel();
// 주문한거 하나하나 다 취소(다대일 관계이기 때문에 하나하나 다 삭제)
}
}
//==조회 로직==//
/**
* 전체 주문 가격 조회
*/
public int getTotalPrice() { // 주문 수량 * 주문가격
int totalPrice = 0; // 0으로 초기화
for (OrderItem orderItem : orderItems) { // orderItems를 루프를 돌면서
totalPrice += orderItem.getTotalPrice(); // orderItem에 있는 TotalPrice를 가져옴
}
return totalPrice;
}
}
주문상품 엔티티 코드
package jpabook.jpashop.domain;
import jakarta.persistence.*;
import jpabook.jpashop.domain.item.Item;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.cache.spi.support.AbstractReadWriteAccess;
import static jakarta.persistence.FetchType.LAZY;
@Entity
@Table(name = "order_item")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 값을 따로 주려고 할때 못하도록 알아차리게 해주는 코드?(질문)
public class OrderItem {
@Id
@GeneratedValue
@Column(name = "order_item_id") //(FK)
private Long id;
@ManyToOne(fetch = LAZY)// 다대일
@JoinColumn(name = "item_id") //(FK) item을 참조할거니까 FK
private Item item; //주문 상품
@ManyToOne(fetch = LAZY) //다대일
@JoinColumn(name = "order_id")
// 하나의 Order가 여러개의 Orderitem을 가질 수 있지만, 반대로 아이템은 하나의 오더만 가능함.
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);
//Item이 가지고 있는 것이기 때문에 재고수량을 다시 롬복(늘려줌)해줌.
}
//==조희 로직==//
/**
* 주문 상품 전체 가격 조회
*/
public int getTotalPrice() { //totalPrice 계산
return getOrderPrice() * getCount();
}
}
주문 레퍼지토리
package jpabook.jpashop.repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.*;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.domain.Order;
import jpabook.jpashop.domain.OrderSearch;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class OrderRepository {
private final EntityManager em; // 주입
public void save(Order order) { // order 넣고
em.persist(order);
}
public Order findOne(Long id) { //단건 조회
return em.find(Order.class, id);
}
//public List<Order> findAll(OrderSearch orderSearch) {}
주문 서비스
package jpabook.jpashop.service;
import jpabook.jpashop.domain.*;
import jpabook.jpashop.domain.item.Item;
import jpabook.jpashop.repository.ItemRepository;
import jpabook.jpashop.repository.MemberRepository;
import jpabook.jpashop.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import org.aspectj.weaver.ast.Or;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService {
//밑에서 조회할때 사용하려고 설정해놓은것.
private final MemberRepository memberRepository;
private final OrderRepository orderRepository;
private final ItemRepository itemRepository;
/** 주문 */
@Transactional
public Long order(Long memberId, Long itemId, int count) {
// 상품을 선택할때 상품 Id만 넘어오는 것이기 때문에 repository가 많이 필요함.
//엔티티 조회
Member member = memberRepository.findOne(memberId); // 멤버 찾기
Item item = itemRepository.findOne(itemId); // 아이템id가져오기
//배송정보 생성
Delivery delivery = new Delivery(); //생성자 만들고
delivery.setAddress(member.getAddres()); //생성자에 address 설정하고
delivery.setStatus(DeliveryStatus.READY); // 생성자의 배달상태를 준비로 설정.
//주문상품 생성
OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(),count);
// 라이프 사이클? 왔?(질문)
// 다른곳에서 참조할수없는 private 오너라면 유용하게 사용함.
// 생성 메서드 createOrderItem 사용
//주문 생성
Order order = Order.createOrder(member,delivery,orderItem);
//생성 메서드 createOrder
//주문 저장
orderRepository.save(order);
//(질문)
// cascade 덕분에 이 한줄만 써도 delivery랑 orderItem이 persist가 자동으로 됨.
return order.getId(); //오더 식별자값 반환
}
/** 주문 취소 */
@Transactional
public void cancelOrder(Long orderId) {
//주문 엔티티 조회
Order order = orderRepository.findOne(orderId); //리포지토리에서 아이디 찾음
//주문 취소
order.cancel(); // 찾아서 취소하면 끝, (트랙젝션 스크립트?) 왜?(질문)
// 엔티티의 핵심비즈니스 로직을 몰아넣는 것을 도메인 모델 패턴이라고 한다.
}
/** 주문 검색 */
public List<Order> findOrders(OrderSearch orderSearch) {
return orderRepository.findAll(orderSearch);
}
}
- 주문서비스는주문 엔티티와주문상품엔티티의비즈니스로직을 활용해서주문, 주문취소, 주문 내역 검색기능을제공한다.
참고 : 주문서비스의 주문과주문취소 메서드를보면 비즈니스로직 대부분이엔티티에있다. 서비스 계층은단순히 엔티티에필요한 요청을위임하는역할을 한다. 이처럼 엔티티가비즈니스로직을 가지고 객체지향의특성을 적극 활용하는 것 을 도메인 모델패턴(http://martinfowler.com/eaaCatalog/ domainModel.html)이라한다. 반대로 엔티티에는 비즈니스로직이 거의 없고서비스 계층에서 대부분의 비즈니스 로직을 처리하는 것을 트랜잭션 스크립트 패턴(http://martinfowler.com/ eaaCatalog/transactionScript.html)이라 한다.
출처 : 인프런 강의: 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발