Spring/JPA 1

6. 주문 도메인 개발

wch_t 2023. 11. 7. 06:49

1. 도메인 모델 패턴 vs 트랜잭션 스크립트 패턴

도메인 모델 패턴

- Entity에 비즈니스 로직을 구현

- Service 계층은 주로 엔티티에 필요한 요청을 위임하는 역할

- Entity에 대한 테스트를, 데이터베이스나 JPA와 관계없이 쉽게 만들 수 있음.

   ex. (실무에서는) Item Entity에 대한 removeStock() 메서드의 단위테스트를 만들어주는 게 낫다.

 

 

트랜잭션 스크립트 패턴 (일반적인 SQL 패턴)

- Service 계층에 비즈니스 로직을 구현

- Entity는 단순하게 데이터를 전달하는 역할

 

 

정리

- 프로젝트의 성격과 요구사항에 따라 선택할 수 있다.

 

- 도메인 모델 패턴은 객체지향적이며 엔티티 중심의 설계를 지향, 엔티티의 비즈니스 로직을 명확하게 표현할 때 유용하다.

- 트랜잭션 스크립트 패턴은 간단하고 직접적인 데이터베이스 상호 작용이 필요한 경우에 유용하다.

 

 

 

 

 

2. 주문 도메인 개발

1)생성 메서드

- Entity를 생성할 때, 기본 설정을 위해 필요한 정보는 매개변수로 받아 생성한다.

   + DTO를 사용하여 생성할 수도 있음

 

@NoArgsConstructor(access) = AccessLevel.PROTECTED)

: Lombok 라이브러리, 외부에서 엔티티를 생성하는 것을 방지한다.

 

[jpashop.domain.Order.java]

//@NoArgsConstructor(access = AccessLevel.PROTECTED) 어노테이션으로 대체 가능
protected 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;
}

 

[jpashop.domain.OrderItem.java]

protected OrderItem(){}

//==생성 메서드==//
//OrderItem이 생성할 때, 기본 설정
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;
}

 

 

2) 비즈니스 로직

[jpashop.domain.Order.java]

//==비즈니스 로직==//
//주문 취소
public void cancel(){
    if (delivery.getStatus() == DeliveryStatus.COMP) {
        throw new IllegalStateException("이미 배송완료된 상품은 취소가 불가능합니다.");
    }

    this.setStatus(OrderStatus.CANCEL);
    for (OrderItem orderItem : orderItems) {
        orderItem.cancel();
    }
}

 

[jpashop.domain.OrderItem.java]

//==비즈니스 로직==//
public void cancel() {
    getItem().addStock(count);
}

 

 

3) 조회 로직

[jpashop.domain.Order.java]

//==조회 로직==//
//전체 주문 가격 조회
public int getTotalPrice() {
    int totalPrice = orderItems.stream()
            .mapToInt(OrderItem::getTotalPrice)
            .sum();

//        for (OrderItem orderItem : orderItems) {
//            totalPrice += orderItem.getTotalPrice();
//        }

    return totalPrice;
}

 

[jpashop.domain.OrderItem.java]

//==조회 로직==//
//주문상품 전체 가격 조회
public int getTotalPrice() {
    return getOrderPrice() * getCount();
}

 

 

 

 

 

3. Cascade

[jpashop.service.OrderService.java]

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

 

"orderRepository.save(order)"를 할 때, em.persist(order)가 된다.

이 때 order와 연관된 엔티티인 orderItems, delivery도 같이 persist 된다.

 

+ 주의할 점

<Private Owner>인 경우에만 cascade를 사용해야 한다.

즉, 연관 관계에서 1개의 엔티티만 다른 엔티티가 연관되어 있을 때이다.

 

Why?

   여러 엔티티와 연관관계를 갖는 상황에서,

   cascade 설정한 특정 엔티티를 삭제할 때 같이 삭제될 수 있으므로

   다른 엔티티에서도 참조하는 경우에는 cascade를 사용하면 안된다.

 

 

 

 

 

4. 업데이트와 변경 감지 (Dirty Checking)

[개념]

엔티티의 필드 값을 변경하면,

jpa가 영속성 컨텍스트 내의 상태 변화를 감지하고, 변경된 엔티티를 자동으로 데이터베이스에 반영한다.

 

 

[동작 방식]

  1. 영속성 컨텍스트(Persistence Context)에 엔티티를 로드하거나 새로 생성하면 엔티티의 상태는 '관리(Managed)' 상태가 됩니다. 이때, 영속성 컨텍스트는 엔티티의 스냅샷(현재 상태)을 기록합니다.
  2. 엔티티의 상태를 변경하면 JPA는 이러한 변경을 감지하고 엔티티를 '더티(Dirty)' 상태로 표시합니다. 이것은 엔티티가 영속성 컨텍스트 내에서 수정되었음을 나타내는 것입니다.
  3. 트랜잭션을 커밋하거나 flush를 수행할 때, JPA는 더티 상태인 엔티티를 데이터베이스와 동기화합니다. JPA는 스냅샷과 현재 상태를 비교하고 변경된 엔티티 속성을 UPDATE 쿼리를 사용하여 데이터베이스에 반영합니다.
  4. 데이터베이스와의 동기화가 이루어진 후, 엔티티는 다시 '영속(Managed)' 상태가 되며 영속성 컨텍스트 내에서 변경사항이 추적됩니다.

 

[장점]

- 개발자가 명시적으로 엔티티 상태 변경 및 데이터베이스 update 쿼리를 작성하지 않아도 된다.

   (JPA가 엔티티의 변경을 자동으로 감지하고, 데이터베이스와의 동기화를 관리)