JPA

JPA 엔티티의 객체지향적 설계

devJK93 2024. 12. 9.

package jpashop.jpaorder.domain;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "ORDERS")
public class Order {

  @Id @GeneratedValue
  @Column(name = "ORDER_ID")
  private Long id;
  @Column(name = "MEMBER_ID")
  private Long memberId;
  private LocalDateTime orderDate;
  @Enumerated(EnumType.STRING)
  private OrderStatus orderStatus;
}

 

이런 방식의 설계는 객체지향스럽지 않다 RDB에 맞춘 설계이다 객체지향스럽게 설계를 맞추려면?

 


📝 객체지향적 설계

1. 문제점

  • MEMBER_ID 필드가 외래 키로 사용되지만 연관된 Member 객체를 표현하지 않음: 현재 MEMBER_ID는 단순히 외래 키로 존재하며, Member 객체에 대한 직접적인 참조가 없습니다.
  • 주문 상태를 표현하는 OrderStatus는 적절하지만, 주문 항목(OrderItem)을 포함하지 않음: Order는 개별 주문 항목(상품, 수량 등)을 포함해야 객체지향적으로 의미 있는 상태를 가질 수 있습니다.
  • 행위 중심의 설계 부족: 현재 엔티티는 단순히 데이터를 보관하는 역할만 수행하며, 비즈니스 로직(예: 주문 생성, 상태 변경)이 없습니다.

2. 객체지향적으로 설계하기

2.1 연관 관계 매핑

외래 키(MEMBER_ID)를 제거하고, 대신 Member 엔티티와의 연관 관계를 매핑합니다. 주문 항목(OrderItem)을 Order에 포함하여 OrderOrderItem 간의 관계를 명확히 합니다.

2.2 비즈니스 로직 추가

Order 클래스에 책임(행위)을 추가하여, 객체가 스스로의 상태를 관리하도록 합니다.

객체지향적 설계 코드

Order 엔티티

package jpashop.jpaorder.domain;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "ORDERS")
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, orphanRemoval = true)
    private List<OrderItem> orderItems = new ArrayList<>();

    private LocalDateTime orderDate;

    @Enumerated(EnumType.STRING)
    private OrderStatus orderStatus;

    //== 연관 관계 편의 메서드 ==//
    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 changeStatus(OrderStatus status) {
        this.orderStatus = status;
    }

    public void calculateTotalPrice() {
        int totalPrice = orderItems.stream()
                                   .mapToInt(OrderItem::getTotalPrice)
                                   .sum();
        System.out.println("Total Price: " + totalPrice);
    }
}

Member 엔티티

package jpashop.jpaorder.domain;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "MEMBER")
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();

    //== 연관 관계 편의 메서드 ==//
    public void addOrder(Order order) {
        orders.add(order);
        order.setMember(this);
    }
}

OrderItem 엔티티

package jpashop.jpaorder.domain;

import javax.persistence.*;

@Entity
@Table(name = "ORDER_ITEM")
public class OrderItem {

    @Id
    @GeneratedValue
    @Column(name = "ORDER_ITEM_ID")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ORDER_ID")
    private Order order;

    private String itemName;
    private int price;
    private int quantity;

    //== 비즈니스 로직 ==//
    public int getTotalPrice() {
        return price * quantity;
    }
}

OrderStatus Enum

package jpashop.jpaorder.domain;

public enum OrderStatus {
    NEW, PROCESSING, COMPLETED, CANCELLED
}

객체지향 설계의 장점

  • 직관적인 관계 표현: Order와 Member, OrderItem 간의 관계를 객체적으로 표현하여 더 자연스럽습니다.
  • 행위 중심 설계: Order와 OrderItem이 각자의 비즈니스 로직을 포함하여 객체 스스로 상태를 관리합니다.
  • 유지보수 용이성: 객체 간 관계와 비즈니스 로직이 명확히 분리되므로, 변경이 용이합니다.
  • 확장 가능성: Order에 결제 정보, 배송 정보 등을 추가할 때 객체 간 관계를 쉽게 확장할 수 있습니다.

결론

객체지향적으로 설계하려면:

  • RDB 설계에 맞춘 외래 키 중심의 설계를 제거하고, 객체 간 연관 관계를 표현합니다.
  • 객체가 상태와 행위를 가지도록 비즈니스 로직을 추가합니다.

이렇게 하면 코드가 더 명확해지고, 유지보수성과 확장성이 높은 설계를 만들 수 있습니다. 😊

댓글