자바 ORM 표준 JPA 프로그래밍 - 웹 애플리케이션 제작(2)

Song Chae Won·2023년 8월 7일
0
post-thumbnail

📝 웹 애플리케이션 제작(2)

도메인 모델과 테이블 설계

요구사항 분석

작은 쇼핑몰을 설계하고 만들어보자 !

(1) 회원 기능

  • 회원 등록
  • 회원 조회

(2) 상품 기능

  • 상품 등록
  • 상품 수정
  • 상품 조회

(3) 주문 기능

  • 상품 주문
  • 주문 내역 조회
  • 주문 취소

(4) 기타 요구사항

  • 상품의 종류는 도서, 음반, 영화가 있다.
  • 상품을 카테고리로 구분할 수 있다.
  • 상품 주문 시 배송 정보를 입력할 수 있다.

도메인 모델 설계

요구사항을 분석해서 설계한 도메인 모델

🔻 회원, 주문, 상품의 관계

  • 회원은 여러 상품을 주문할 수 있음
  • 한 번 주문할 때 여러 상품을 선택할 수 있으므로 주문과 상품은 다대다 관계
  • 다대다 관계는 관계형 데이터베이스는 물론이고 엔티티에서도 거의 사용하지 않으므로 주문상품이라는 엔티티를 추가해서 다대다 관계를 일대다, 다대일 관계로 풀어냄

🔻 상품 분류

  • 상품은 도서, 음반, 영화로 구분되는데 상품이라는 공통 속성을 사용하므로 상속 구조로 표현

엔티티 분석

  • 회원 (Member): 이름(name)과 주문한 상품들(orders) 그리고 임베디드 타입인 주소(Address)를 가짐
  • 주문 (Order)
    - 한 번 주문 시 여러 상품을 주문할 수 있으므로 주문과 주문상품은 일대다 관계
    - 주문은 상품을 주문한 회원(member)과 배송 정보(delivery), 주문 날짜(orderDate), 주문 상태(status)를 가지고 있음
    - 주문 상태는 열거형을 사용해 주문, 취소를 표현할 수 있음
  • 주문상품 (OrderItem): 주문한 상품 정보가 주문 금액(orderPrice), 주문 수량(count) 정보를 가지고 있음
  • 상품 (Item)
    - 이름(name), 가격(price), 재고수량(stockQuantity)을 가지고 있음
    - 상품을 주문하고 재고수량이 줄어들고, 상품의 종류로는 도서, 음반, 영화가 있으며 각각은 사용하는 속성이 조금씩 다름
  • 배송 (Delivery)
    - 주문 시 하나의 배송 정보를 생성함
    - 주문과 배송은 일대일 관계
  • 카테고리 (Category)
    - 상품과 다대다 관계를 맺음
  • 주소 (Address)
    - 값 타입(임베디드 타입)이며 회원과 배송에서 사용

테이블 설계

🔻 객체 도메인 모델을 매핑하기 위해 설계한 테이블

MEMBER

  • 회원 엔티티의 Address 임베디드 타입 정보가 회원 테이블에 그대로 들어가며
    DELIVERY 테이블도 마찬가지

ITEM

  • 앨범, 도서, 영화 타입을 통합해서 하나의 테이블로 만들었으며 DTYPE 컬럼으로 타입을 구분

연관관계 정리

🔽 회원과 주문

  • 일대다 양방향 관계이므로 외래 키가 있는 주문이 연관관계의 주인
  • 그러므로 Order.member를 ORDERS.MEMBER_ID 외래 키와 매핑

🔽 주문상품과 주문

  • 다대일 양방향 관계이므로 주문상품이 연관관계의 주인
  • 그러므로 OrderItem.order를 ORDER_ITEM.ORDER_ID 외래 키와 매핑

🔽 주문상품과 상품

  • 다대일 단방향 관계이므로 OrderItem.item을 ORDER_ITEM.ITEM_ID 외래 키와 매핑

🔽 주문과 배송

  • 일대일 양방향 관계이므로 Order.delivery를 ORDERS.DELIVERY_ID 외래 키와 매핑

🔽 카테고리와 상품

  • @ManyToMany를 사용해서 매핑

단, 실무에서는 복잡성과 관리 어려움의 이유 등으로 @ManyToMany를 잘 사용하지 않지만 다양한 매핑 방법 실습을 위해 추가됨!

엔티티 클래스

  • 회원 엔티티
@Entity
public class Member {

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

    private String name;

    @Embedded
    private Address address;

    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<Order>();
   
    // Getter, Setter
    ...
}
  • 주문 엔티티
@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)
    private List<OrderItem> orderItems = new ArrayList<OrderItem>();

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "DELIVERY_ID")
    private Delivery delivery; // 배송정보

    private Date 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);
    }

    // Getter, Setter
    ...
}
  • 주문상품 엔티티
@Entity
@Table(name = "ORDER_ITEM")
public class OrderItem {

    @Id @GeneratedValue
    @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; // 주문 수량

    // Getter, Setter
    ...
}
  • 상품 엔티티
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {

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

    private String name; // 이름
    private int price; // 가격
    private int stockQuantity; // 재고수량

    @ManyToMany(mappedBy = "items")
    private List<Category> categories = new ArrayList<Category>();
    
    // Getter, Setter
    ...
}
  • 상품 - 도서 엔티티
@Entity
@DiscriminatorValue("B")
public class Book extends Item {

    private String author;
    private String isbn;
    
    // Getter, Setter
    ...
}
  • 상품 - 음반 엔티티
@Entity
@DiscriminatorValue("A")
public class Album extends Item {

    private String artist;
    private String etc;
    
    // Getter, Setter
    ...
}
  • 상품 - 영화 엔티티
@Entity
@DiscriminatorValue("M")
public class Movie extends Item {

    private String director;
    private String actor;
    
    // Getter, Setter
    ...
}
  • 배송 엔티티
@Entity
public class Delivery {

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

    @OneToOne(mappedBy = "delivery")
    private Order order;

    @Embedded
    private Address address;

    @Enumerated(EnumType.STRING)
    private DeliveryStatus status; // ENUM [READY(준비), COMP(배송)]
    
    // Getter, Setter
    ...
}
  • 카테고리 엔티티
@Entity
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<Item>();

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

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

    //==연관관계 메서드==//
    public void addChildCategory(Category child) {
        this.child.add(child);
        child.setParent(this);
    }
    
    // Getter, Setter
    ...
}
  • 주소 값 타입
@Embeddable
public class Address {

    private String city;
    private String street;
    private String zipcode;
    
    // Getter, Setter
    ...
}

위와 같은 코드로 엔티티와 테이블 설계를 완성하였다. 이 엔티티를 기반으로 다음 포스팅에서는 실제 애플리케이션을 개발해보자!

➕ 참고 : 기본 키 이름과 엔티티 식별자 이름
테이블의 기본 키 컬럼 이름은 해당 테이블 이름을 포함하도록 함. 엔티티는 연관된 엔티티를 참조할 때 타입이 있으므로 엔티티의 식별자를 단순히 id로 지어도 어떤 엔티티를 참조하는지 쉽게 알 수 있지만, 테이블의 기본 키나 외래 키는 단순히 값만 저장하지 어떤 테이블과 관계가 있는지 외래 키 제약조건을 참고하지 않으면 알 수 없다!

profile
@chhaewxn

0개의 댓글