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가 영속성 컨텍스트 내의 상태 변화를 감지하고, 변경된 엔티티를 자동으로 데이터베이스에 반영한다.
[동작 방식]
- 영속성 컨텍스트(Persistence Context)에 엔티티를 로드하거나 새로 생성하면 엔티티의 상태는 '관리(Managed)' 상태가 됩니다. 이때, 영속성 컨텍스트는 엔티티의 스냅샷(현재 상태)을 기록합니다.
- 엔티티의 상태를 변경하면 JPA는 이러한 변경을 감지하고 엔티티를 '더티(Dirty)' 상태로 표시합니다. 이것은 엔티티가 영속성 컨텍스트 내에서 수정되었음을 나타내는 것입니다.
- 트랜잭션을 커밋하거나 flush를 수행할 때, JPA는 더티 상태인 엔티티를 데이터베이스와 동기화합니다. JPA는 스냅샷과 현재 상태를 비교하고 변경된 엔티티 속성을 UPDATE 쿼리를 사용하여 데이터베이스에 반영합니다.
- 데이터베이스와의 동기화가 이루어진 후, 엔티티는 다시 '영속(Managed)' 상태가 되며 영속성 컨텍스트 내에서 변경사항이 추적됩니다.
[장점]
- 개발자가 명시적으로 엔티티 상태 변경 및 데이터베이스 update 쿼리를 작성하지 않아도 된다.
(JPA가 엔티티의 변경을 자동으로 감지하고, 데이터베이스와의 동기화를 관리)
'Spring > JPA 1' 카테고리의 다른 글
7. 웹 계층 개발 (0) | 2023.11.13 |
---|---|
5. 상품 도메인 개발 (0) | 2023.11.06 |
4. 회원 도메인 개발 (0) | 2023.11.06 |
3. 애플리케이션 구현 준비 (0) | 2023.11.06 |
2. 도메인 분석 설계 (0) | 2023.08.16 |