1. 주문 Entity와 도메인 모델 패턴
다음 패턴은 OrderService에서 정의되어야 할 여러 로직들을 Entity에서 정의하는 도메인 모델 패턴의 코드이다. 주문 생성, 취소, 조회를 담당하는 코드를 작성하였다.
1) @NoArgsConstructor(access = AccessLevel.PROTECTED)
앞서 설명하였듯 도메인 모델 패턴의 코드에서는 Entity에서 비즈니스 로직을 정의하고, Service에서는 이를 호출하여 기능을 구현하는 것이다. 그렇다면 이전 방법처럼 new를 통해 객체를 생성하고 값을 주입하는 방법은 사용할 수 없을까?
// 도메인 모델 패턴
OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count);
// 이전 로직 구성 방식
OrderItem orderItem = new OrderItem()
orderItem.item = item;
...
아니다. 하지만 둘 중에서 통일하여 사용하는 것이 후에 있을 유지 보수에 있어 더 도움이 된다.
이러한 경우 new를 통해 객체를 생성하고 값을 주입하는 방법을 제한하기 위해서는 protected 로 선언하거나,
protected Order() { }
@NoArgsConstructor(access = AccessLevel.PROTECTED) 어노테이션을 붙이면 된다.
# Order Entity 코드
@Entity
@Table(name = "orders")
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
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) {
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() {
// 비즈니스 로직에 대한 체크 로직이 Entity안에 있다
if (delivery.getStatus() == DeliveryStatus.COMP) {
throw new IllegalStateException("이미 배송 완료된 상품은 취소가 불가합니다.");
}
this.setStatus(OrderStatus.CANCEL);
// 꼭 this를 써야하는 경우도 있지만, 평소에는 안쓰고 로직에서 강조할때 사용
for (OrderItem orderItem: this.orderItems) {
orderItem.cancel();
}
}
//==조회 로직==//
/**
* 전체 주문 가격 조회
*/
public int getTotalPrice() {
int totalPrice = 0;
for (OrderItem orderItem: orderItems) {
totalPrice += orderItem.getTotalPrice();
}
return totalPrice;
// return orderItems.stream()
// .mapToInt(OrderItem::getTotalPrice)
// .sum();
}
}
2. 주문 Repository
@RequiredArgsConstructor로 final로 선언한 EntityManager를 생성하고, 이전과 동일하게 save, findOne을 구현한다.
@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);
}
// public List<Order> findAll(OrderSearch orderSearch) { }
}
3. 주문 Service
Entity에 정의한 비즈니스 로직을 호출하기만 해서 로직을 구현하였다.
@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) {
// Entity 조회
Member member = memberRepository.findOne(memberId);
Item item = itemRepository.findOne(itemId);
// 배송 정보 생성
Delivery delivery = new Delivery();
delivery.setAddress(member.getAddress());
delivery.setStatus(DeliveryStatus.READY);
// 주문 상품 생성
OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count);
// 주문 생성
Order order = Order.createOrder(member, delivery, orderItem);
// 주문 저장
orderRepository.save(order); // Cascade 옵션이 있기 때문에, orderItem, delivery는 persist되면서 DB 테이블에 같이 insert된다
return order.getId();
}
// 취소
@Transactional
public void cancelOrder(Long orderId) {
Order order = orderRepository.findOne(orderId);
order.cancel();
}
// 검색
}
'🌱 Spring > JPA ①' 카테고리의 다른 글
[Spring] Item 수정(준영속 엔티티 수정 - 변경 감지와 merge) (0) | 2023.08.24 |
---|---|
[Spring] Member/Item 등록, 조회(필수 입력 필드 처리) (0) | 2023.08.23 |
[Spring] 상품 Entity, Repository, Service 개발 (0) | 2023.08.05 |
[Spring] 회원 Service Unit Test 작성하기 (0) | 2023.08.05 |
[Spring] 회원 Service & Repository 개발 (0) | 2023.08.04 |