주문 엔터티 개발
@Entity, @Table를 통해 엔터티 계층임을 지정하고 DB에서 매핑할 테이블명을 설정한다.
@Getter와 @Setter는 해당클래스의 조회와 설정을 허용함을 의미하는데, 엔터티의 값은 불변성을 유지해야 하므로 보통의 경우에는 Setter는 사용하지 않는다.
@Entity
@Table(name = "orders")
@Getter
public class Order {
주문 엔터티를 중심으로 보면, 연결된 엔터티간의 관계에서 아래와 같은 필드를 가진다.
- 회원 id (FK) -> 한 명의 회원은 여러 주문 정보를 가질 수 있다.
Member 엔터티와 다대일 양방향 관계를 설정한다. @JoinColumn은 member_id에 FK를 지정한다.
FetchType.LAZY를 사용해 지연로딩을 설정, 이는 실제 사용시 로딩됨을 의미한다.
- 배송 id (FK) -> 하나의 주문은 하나의 배송 정보를 가진다.
Delivery 엔터티와 일대일 단방향 관계를 설정한다. 회원 id외 마찬가지로 delivery_id에 FK를 지정한다.
지연로딩 역시 마찬가지다. 그러나 CasacadeType을 설정해 생명주기를 관리한다는 점이 회원id와 차이가 있다.
이는 모든 변경 내용이 적용됨을 의미하는데, 주문과 배송의 관계에서 서로 반드시 필요한 존재이기 때문이다.
- 주문 리스트 -> 하나의 주문은 여러 개의 주문 상품을 가질 수 있다.
연관관계를 맺을 때는 주인이 되는 엔터티를 결정해야 한다. 주인 엔터티가 DB에 직접적인 영향을 주는 주체이며, 주인이 아닌 엔터티는 단순 참조 역할을 한다.
주로 FK키 값을 지닌 쪽이 주인의 역할을 맡는다. 반대로 주인이 아닌 쪽은 mappedBy 속성을 통해 매핑된 필드를 지정한다.
private List<OrderItem> orderItems = new ArrayList<>();는 크기 조절에 동적인 자료구조를 통해 OrderItem 엔터티를 담는 컬렉션을 구현했다.
연관관계의 주인 측에 연관 관계 메서드를 설정한다.
불변성을 유지시키기 위해 필요한 메서드에 한하여 Setter를 사용할 수 있다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "delivery_id")
private Delivery delivery;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
//연관관계 메서드
public void setMember(Member member) {
this.member = member;
member.getOrders().add(this);
}
public void setDelivery(Delivery delivery) {
this.delivery = delivery;
delivery.setOrder(this);
}
public void addOrderItem(OrderItem orderItem) {
orderItems.add(orderItem);
orderItem.setOrder(this);
}
주문 엔터티 자체에 속하며, 주문을 생성하는 정적인 메서드/
빈 주문 객체를 생성하고 회원 정보와, 배송 정보를 설정한다.
for문을 통해 주문 상품을 주문 리스트에 추가한다. 추가하는 동작은 연관관계 메서드에서 만든 addOrderItem을 사용한다.
주문 상태는 주문됨, 주문 일자는 현재 시각을 설정한다.
완성된 주문을 반환한다.
//생성 메서드
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.setOrderStatus(OrderStatus.ORDER);
order.setOrderdate(LocalDateTime.now());
return order;
}
주문을 취소하는 cancel메서드이다.
배송의 상태를 조회해서 배송상태가 COMP이면, IllegalStateException 예외 처리.
주문 객체의 주문상태를 취소됨으로 설정한다.
for문을 통해 주문에 속한 주문상품을 취소한다.
//비즈니스 로직
//주문 취소
public void cancel() {
if (delivery.getStatus() == DeliveryStatus.COMP) {
throw new IllegalStateException("이미 배송완료된 상품은 취소가 불가능합니다.");
}
this.setOrderStatus(OrderStatus.CANCEL);
for (OrderItem orderItem : orderItems) {
orderItem.cancel();
}
}
TotalPrice를 0으로 초기화한 뒤, for문을 통해 주문상품들을 돌며 주문상품의 전체 가격을 더한다.
//조회 로직
//전체 주문 가격 조회
public int getTotalPrice() {
int totalPrice = 0;
for (OrderItem orderItem : orderItems) {
totalPrice += orderItem.getTotalPrice();
}
return totalPrice;
}
주문 상품 엔터티 개발
@Entity
@Table(name = "order_item")
@Getter
@Setter
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@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;
}
createOrder와 createOrderItem의 관계 설명:
createOrderItem 메서드를 사용하여 주문 상품들을 생성하고, 이들을 createOrder 메서드에 전달하여 주문을 생성한다.
//비즈니스 로직
//주문 취소시 재고를 다시 증가시킴.
public void cancel() {
getItem().addStock(count);
}
//조회 로직
//주문상품 전체 가격 조회 -> 주문가격과 수량을 조회해 곱셈.
public int getTotalPrice() {
return getOrderPrice()*getCount();
}
주문 리포지토리 개발
리포지토리 개발 내용 참고.
@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);
}
주문 서비스 개발
@Service계층, 읽기 전용, 생성자 주입을 통해 틀을 갖춘다.
주문 서비스는 회원, 주문, 상품 리포지토리를 모두 주입받는다.
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService {
private final MemberRepository memberRepository;
private final OrderRepository orderRepository;
private final ItemRepository itemRepository;
주문 서비스는 주문과 주문상품 엔터티에서 생성한 비즈니스 로직을 통해 기능 위임한다.
=> 도메인 모델 패턴을 통해 비즈니스 로직을 구현하는 메서드를 엔터티에서 사용하고 있기 때문에, 서비스 계층은 구현이 아닌 로직을 호출하고 트랜잭션을 관리하여, DB에 반영하는 역할을 수행한다.
=> 이후, 도메인 주도 설계 DDD에 대해 공부할 것.
회원id, 상품id, 수량을 변수로 받아 id값을 통해 리포지토리에서 반환했던 엔터티 값을 조회한다.
빈 배송정보 객체 생성한 뒤, 받아온 회원 엔터티의 주소 정보를 배송정보의 주소정보로 설정한다.
도메인에서 생성한 비즈니스 로직인 주문상품과 주문을 생성한다.
리포지토리에 구현된 주문 정보를 저장하고 id값을 반환한다.
//주문
@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();
}
'Java Spring > Spring Boot' 카테고리의 다른 글
웹 계층 컨트롤러 개발 Spring Boot 기본 (5) (0) | 2023.12.12 |
---|---|
상품 도메인 개발 Spring Boot 기본 (3) (0) | 2023.12.11 |
회원 도메인 개발 Spring Boot 기본 (2) (1) | 2023.12.08 |
도메인 설계 Spring Boot 기본 (1) (1) | 2023.12.07 |