Domain Design

Function Design

  • 회원 기능
    • 회원 등록
    • 회원 조회
  • 상품 기능
    • 상품 등록
    • 상품 수정
    • 상품 조회
  • 주문 기능
    • 상품 주문
    • 주문 내역 조회
    • 주문 취소
  • 기타 요구사항
    • 상품은 재고 관리가 필요하다.
    • 상품의 종류는 도서, 음반, 영화가 있다.
    • 상품을 카테고리로 구분할 수 있다.

Entity Design

entity_design

연관관계 설정

  • 회원과 주문: 다대일 양방향 관계를 가진다. 회원 객체가 외래키를 가지기 때문에 회원 객체를 연관관계의 주인으로 설정해서 연관관계를 매핑한다.
  • 주문상품과 주문: 다대일 양방향 관계로 외래키를 가지는 주문상품을 연관관계를 주인으로 설정한다.
  • 주문상품과 상품: 다대일 단방향 관계
  • 주문과 배송: 일대일 양방향 관계로 설정하고, 연관관계의 주인은 주문 객체로 설정한다.
  • 카테고리와 상품은 다대다 관계를 가진다.

Table Design

table_design

테이블 설계를 확인해보면 entity에 임베디드 타입으로 설정된 Address 객체에 대해서 컬럼단위로 풀어서 입력되는 것을 확인할 수 있다. 또한, Item 객체는 Album, Book, Movie 3가지 subclass을 가지는데, Single Table 전략을 취해서 DTYPE을 구분한다.

Entity JPA Codes

Member

@Entity
@Data
public class Member {
    @Id
    @GeneratedValue
    @Column(name="member_id")
    private Long id;

    private String name;

    @Embedded
    private Address address;

    @OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
    private List<Order> orders = new ArrayList<>();
}

Order

@Entity
@Table(name = "ORDERS")
@Data
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<>();

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name="delivery_id")
    private Delivery delivery;

    private LocalDateTime orderDate;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;

    //연관관계 설정 메소드
    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);
    }
}

Delivery

@Entity
@Data
public class Delivery {
    @Id
    @GeneratedValue
    @Column(name="delivery_id")
    private Long id;

    @OneToOne(mappedBy = "delivery",fetch = FetchType.LAZY)
    private Order order;

    @Embedded
    private Address address;

    @Enumerated(EnumType.STRING)
    private DeliveryStatus status;

}

OrderItem

@Entity
@Data
public class OrderItem {
    @Id
    @GeneratedValue
    @Column(name = "order_item_id")
    private Long id;

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

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="item_id")
    private Item item;

    private int orderPrice;

    private int count;
}

Item, Album, Book, Movie

@Entity
@Data
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="dtype")
public abstract class Item {
    @Id
    @GeneratedValue
    @Column(name="item_id")
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "items", cascade = CascadeType.ALL)
    private List<Category> categories = new ArrayList<>();

    private int price;

    private int stockQuantity;
}

@Entity
@Data
@DiscriminatorValue("A")
public class Album extends Item{
    private String artist;
    private String etc;
}

@Entity
@Data
@DiscriminatorValue("B")
public class Book extends Item{
    private String author;
    private String isbn;
}

@Entity
@Data
@DiscriminatorValue("M")
public class Movie extends Item{
    private String director;
    private String actor;
}

Category

@Entity
@Data
public class Category {
    @Id
    @GeneratedValue
    @Column(name="category_id")
    private Long id;

    private String name;
    @ManyToMany
    @JoinTable(
            name="category_item",
            joinColumns = @JoinColumn(name="category_id"),
            inverseJoinColumns = @JoinColumn(name="item_id")
    )
    private List<Item> items = new ArrayList<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="parent_id")
    private Category parent;

    @OneToMany(mappedBy = "parent")
    private List<Category> child = new ArrayList<>();

    //연관관계 설정 메소드
    public void addChildCategory(Category category) {
        this.child.add(category);
        category.setParent(this);
    }
}

Design Policies?

  1. 실무에서는 절대 다대다 매핑을 사용하지 말것

위에서 Item과 Category에 대해 다대다(@ManyToMany) 매핑을 활용하였는데, FK-PK 매핑만 설정할 수 있다, 따로 파생되는 컬럼을 처리할 수 없고, 세밀한 쿼리 수행이 어렵기 때문에 Order 와 Item 간에 관계를 매핑하기 위해 OrderItem 이라는 entity를 생성하여 다대일(@ManyToOne), 일대다(@OneToMany) 관계로 풀어서 매핑하는 것이 좋다

  1. setter는 가급적 활용하지 않는다.

setter을 public으로 열어놓게 되면 변경의 시점이 매우 분산되기 때문에 변경을 제어하기 어려워진다. 따라서 setter 대신, business method을 활용하여 수정을 진행한다. getter은 모두 열어놔도 상관없다

  1. 연관된 컬럼(필드)를 설정할 때 항상 FetchType.LAZY로 설정한다.

다시 말해, 즉시로딩 대신에 지연로딩으로 설정해서 해당 필드에 접근할 때 연관된 정보를 받아오는 방식을 활용한다. 즉시로딩으로 설정하게 되면 모든 연관된 정보를 가져오기 때문에 정보가 필요하지 않음에도 정보를 가져오는 상황이 발생하여 성능 상 문제가 된다. 또한 N+1 문제를 발생시키기도 하기 때문에 모든 연관된 컬럼에 대해서는 lazy(지연로딩)으로 설정한다.

  1. collection type에 대해서는 필드 초기화를 진행한다.

JPA에는 List -> PersistentBag, Set -> PersistentSet 등과 같이 내장객체로 변환하기 때문에 필드 단계에서 초기화를 진행한다.부가적으로 객체 생성시 NPE 문제를 예방할 수 있다.

References

link: inflearn

link:jpa

link:thymeleaf

link: springboot3

댓글남기기