JPA/JPA 활용

회원 도메인 개발

chanhee01 2023. 7. 11. 18:07

사진출처 : 김영한님 인프런 강의

위의 사진과 같이 상품을 주문하는 도메인을 개발할 것이다.

사진출처 : 김영한님 인프런 강의

각각의 Entity들을 살펴보면 관계형 데이터베이스의 의존관계를 통해 필요한 관계형 db들끼리 연관이 되어있다.

사진출처 : 김영한님 인프런 강의

 코드로 작성을 하기 전에 어떤 변수들이 필요한지, FK(foreign key)는 어떤 db의 변수로 지정할 지 등을 먼저 정리하고 코드로 작성하는 것이 좋다.

 

 

관계형 데이터 베이스는 3가지 형태가 있다.

  • ManyToOne : 여러개와 하나의 관계를 나타냄 order에서 member와의 관계
  • OneToMany : 하나와 여러개의 관계를 나타냄 member에서 order의 관계
  • ManyToMany : 여러개 사이의 관계를 나타냄(사용 되도록 하지 않기)

 

일대 다 관계에서는 FK를 다수쪽에 넣기(Member와 Order가 있으면 Order에 FK)
일대 일 관계에서는 FK를 access 많이 하는 곳에 넣기

다대 다 관계는 실무에서 사용하면 안됨

 

 

 

Order.class

@Entity
@Table(name = "orders") // 이거 하지 않으면 관례로 order로 들어감
@Getter @Setter
public class Order {

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

    @ManyToOne
    @JoinColumn(name = "member_id") // FK(foreign key)
    private Member member;

    @OneToMany(mappedBy =  "order") //order에 의해서 mapping이 되었다는 뜻
    private List<OrderItem> orderItems = new ArrayList<>();

    @OneToOne
    @JoinColumn(name = "delivery_id")
    private Delivery delivery;

    private LocalDateTime orderDate; // 주문 시간, 자바에서 기본으로 지원해주는 시간기능

    @Enumerated(EnumType.STRING) // Enum을 string으로 받아온다는 애노테이션
    private OrderStatus status; // 주문 상태 [ORDER, CANCEL] (enum 타입)
}

엔티티를 설계할 때 FK와 의존관계들을 잘 고려해야한다. order 엔티티는 member_id와 delivery_id 대해 FK를 갖는다. FK가 된다는 뜻인 @JoinColumn을 통해 관계를 맺을 value에 접근하는 것이고, FK가 아닌 값은 @JoinColumn 애노테이션을 안 붙이면 된다. OrderItem의 관계에서는 OrderItem이 FK를 갖기 때문에 @joinColum 애노테이션이 없다.

 

 

Member.class

@Entity
@Getter @Setter
public class Member {

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

    private String name;

    @Embedded
    // Address 클래스의 @Embeddable과 이것중 하나만 적어도 되긴하는데 둘 다 적는 편
    private Address address;

    @OneToMany(mappedBy = "member")
    // order table에 있는 member 필드에 의해 매핑되었다는 뜻
    // 연관관계의 주인은 FK가 가까운 곳으로 하면 됨 -> order에 있는 foreign key가 주인이다.
    private List<Order> orders = new ArrayList<>();
}

Member 엔티티는 order 리스트와 관계가 있다. member 하나에 order 여러개가 있을 수 있으므로 @OneToMany 애노테이션을 사용했다. 또한 @Embedded로 Address를 받아왔는데 Member 엔티티에서 사용하는 내장타입의 클래스이다.

 

Address.class

@Embeddable // JPA의 내장타입이기 때문에 @Embeddable
@Getter
public class Address {

    private String city;
    private String street;
    private String zipcode;
}

 

 

 

Delivery.class

@Entity
@Getter @Setter
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) // EnumType은 무조건 String으로 받기
    private DeliveryStatus status; // READY, COMP
}

delivery 엔티티는 order 엔티티와 @OneToOne 관계를 가진다. 또한 위에서 사용했던 Address를 재사용했다.

@Enumerated는 DeliveryStatus라는 enum의 상태를 받아온다는 뜻이다.

 

DeliveryStatus.enum

public enum DeliveryStatus {
    READY, COMP
}

 

 

 

OrderItem.class

@Entity
@Getter @Setter
public class OrderItem {

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

    @ManyToOne
    @JoinColumn(name = "item_id")
    private Item item;

    @ManyToOne
    @JoinColumn(name = "order_id") // order에 있는 id를 매핑
    private Order order;

    private int orderPrice; // 주문 "당시"의 가격
    private int count; // 주문 당시의 수량
}

orderitem 엔티티는 item, order과 관계형 db 관계인데, order_id와 item_id 모두에서 FK인 존재이다.

이렇게 access가 많은 엔티티가 FK를 가지는게 일반적이다.

 

 

 

Item.class

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
@Getter @Setter
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<>();
}

Item은 abstract class로 선언되어서 @Inheritance를 하게 된다.

@DiscriminatorColumn(name = 'dtype')은 item들을 분류하는 에노테이션이다. 예를 들어 Book.class를 보면

@Entity
@DiscriminatorValue("B")
@Getter
@Setter
public class Book extends Item{

    private String author;
    private String isbn;
}

@DiscriminatorValue("B")로 Book을 B로 분류한 것이다.

 

 

 

 

Category.class

@Entity
@Getter @Setter
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<>();

    // ~35까지
    @ManyToOne
    @JoinColumn(name = "parent_id")
    private Category parent;

    @OneToMany(mappedBy = "parent")
    private List<Category> child = new ArrayList<>();
    // 상속관계의 연관관계 (카테고리도 상속이 있을 수 있으니까 (음식에 우유, 빵이 있듯이))
}

카테고리에서 ManyToMany를 사용했는데 @JoinTable이라는 중간 테이블 매핑을 통해 category_item이라는 테이블을 생성해서 다대 다를 일대 다, 다대 일로 분리해주는 @JoinTable이 필요하다. 하지만 필드를 추가한다거나 그런 기능은 안되고 단순히 서로 관계 매핑만 되기 때문에 실무에서는 사용하지 않는다고 한다.

 

아래 두 변수들은 상속관계의 연관관계이다. 카테고리에도 상속이 있을 수 있으므로 같은 엔티티에서도 관계 매핑을 해준 것이다.