4 분 소요


요구사항 분석

  • 회원은 상품을 주문할 수 있다.
  • 주문시 여러 종류의 상품을 선택할 수 있다.


도메인 모델 분석

  • 회원 - 주문 관계: 회원은 여러번 주문할 수 있다.(일대다)
  • 주문 - 상품 관계: 주문할 때 여러 상품을 선택할 수 있다.
    • 반대로 상품도 여러번 주문될 수 있다.
    • 주문상품 모델을 만들어서 다대다 관계를 일대다, 다대일로 풀어냈다. 15

테이블 설계

16

엔티티 설계와 매핑

17


코드 작성

Member.class

@Entity
public class Member {

    @Id @GeneratedValue()
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;
    private String city;
    private String street;
    private String zipcode;

    // getter, setter...
}

Order.class

@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 status; // ORDER, CANCEL(주문 상태 enum)

    // getter, setter...
}

나머지도 비슷하게 작성하기 때문에 생략..


데이터 중심 설계의 문제점

  • 현재 방식은 객체 설계를 테이블 설계에 맞춘 방식
  • 테이블의 외래키를 객체에 그대로 가져옴
  • 객체 크래프 탐색이 불가능
  • 참조가 없으므로 UML도 잘못됨

문제점 예시

Order 테이블에서 id값을 알고 있고 조회를 통해 member객체를 찾기 위한 과정

Order order = em.find(Order.class, 1L);
Long memberId = order.getMemberId();

Member member = em.find(Member.class, memberId);

객체지향 답지 않은 코드이다..


연관관계 매핑 시작

객체 구조

  • 테이블 구조는 이전과 동일하기 때문에 생략..
  • 객체 구조는 참조를 사용하도록 변경

23

Member.class(V2)

@Entity
public class Member {

    @Id @GeneratedValue()
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;
    private String city;
    private String street;
    private String zipcode;

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

    // getter, setter...
}

Order.class(V2)

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

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

    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    @OneToMany(mappedBy = "order")
    private List<OrderItem> orderItems = new ArrayList<>();

    private LocalDateTime orderDate;

    @Enumerated(EnumType.STRING)
    private OrderStatus status; // ORDER, CANCEL(주문 상태 enum)

    // 예시 코드
    public void addOrderItem(OrderItem orderItem) {
        orderItems.add(orderItem);
        orderItem.setOrder(this);
    }
    // getter, setter...
}

OrderItem.class(V2)

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")
    private Order order;

    private int orderPrice;
    private int count;

    // getter, setter...
}

예시 JpaMain.class

public class JpaMain {
    public Static void main(String[] args) {
        
        ...

        Order order = new Order();
        order.addOrderItem(new OrderItem());
    }
}

public class Order{

    ...

    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
}

이 코드의 의미는 호출한 해당 테이블(Order)에 name의 입력값인 MEMBER_ID라는 외래 키 컬럼이 생성된다.
Order 클래스의 member변수에 Member 엔티티가 할당되면 Order를 저장할 때 Member엔티티의 PK를 외래 키 컬럼인 MEMBER_ID에 저장하게 된다.
PK를 외래 키 컬럼에 저장하는 것이므로 @Column(name = "MEMBER_ID")와 같은 컬럼 설정 코드를 넣지 않아도 정상 동작하는 것이다.


다양한 연관관계 매핑

배송, 카테고리 추가

엔티티

  • 주문과 배송은 1:1(@OneToOne)
  • 상품과 카테고리는 N:M(@ManyToMany)

35

ERD

36

엔티티 상세

37

N:M 관게는 1:N, N:1로

  • 테이블의 N:M 관계는 중간 테이블을 이용해서 1:N, N:1
  • 실무에서는 중간 테이블이 단순하지 않다.
  • @ManyToMany 제약: 필드 추가X, 엔티티 테이블 불일치
  • 실무에서는 @ManyToMany 사용 X

Order.class(V3)

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

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

    // 추가 코드
    @OneToOne
    @JoinColumn(name = "DELIVERY_ID")
    private Delivery delivery;

    ...

    // getter, setter...
}

Delivery.class(추가)

@Entity
public class Delivery {

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

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

    private String city;
    private String street;
    private String zipcode;
    private DeliveryStatus status;  // enum
}

Category.class(추가)

@Entity
public class Category {

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

    private String name;

    @ManyToOne
    @JoinColumn(name = "PARENT_ID")
    private Category parent;

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

    @ManyToMany
    @JoinTable(name = "CATEGORY_ITEM",
            joinColumns = @JoinColumn(name = "CATEGORY_ID"),
            inverseJoinColumns = @JoinColumn(name = "ITEM_ID"))
    private List<Item> items = new ArrayList<>();
}

실무에서 @ManyToMany을 안쓰지만, 예제이기 때문에 예시로 사용함.

Item.class(V3)

@Entity
public class Item {

    ...

    @ManyToMany(mappedBy = "items")
    private List<Category> categoryList = new ArrayList<>();

    ...
}


상속관계 매핑

요구사항 추가

  • 상품의 종류는 음반, 도서, 영화가 있고 이후 더 확장될 수도 있다.
  • 모든 데이터는 등록일과 수정일이 필수이다.

도메인 모델

43

도메인 모델 상세

44

테이블 설계

45

Book.class

@Entity
public class Book extends Item {
    private String author;
    private String isbn;

    // getter and setter ...
}

Album, Movie는 동일하기에 생략..

Item.class(V4)

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn
public abstract class Item extends BaseEntity{

    ...
}
  • 직접 생성해 사용할 일이 없다고 판단하여 추상 클래스로 변경
  • 테이블 설계에 맞춰 단일 테이블 전략으로 설정
  • DTYPE 코드 추가
  • 공통 부분 처리인 BaseEntity(@MappedSuperclass)를 상속받았다.

BaseEntity.class

@MappedSuperclass
public abstract class BaseEntity {

    private String createdBy;
    private LocalDateTime createdDate;
    private String lastModifiedBy;
    private LocalDateTime lastModifiedDate;

    // getter and setter ...
}

모든 클래스(Item.class를 상속받은 클래스 제외)에 BaseEntity를 상속받았다.


연관관계 관리

글로벌 패치 전략 설정

  • 모든 연관관계를 지연 로딩으로 설정
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 지연 로딩으로 변경

영속성 전이 설정

  • Order -> Delivery를 영속성 전이 ALL 설정
  • Order -> OrderItem을 영속성 전이 ALL 설정

Order.class

@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;

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

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> orderItems = new ArrayList<>();

    ...

    // getter, setter...
}

나머지 클래스 설정은 생략..


값 타입 매핑

65

Address.class

@Embeddable
public class Address {

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

    // getter and setter ...

    // equals(), HashCode() ...
}

Member.class

@Entity
public class Member extends BaseEntity{

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

    @Embedded
    private Address address;

    ...

    // getter and setter ...
}

Delivery.class

@Entity
public class Delivery extends BaseEntity{

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

    @Embedded
    private Address address;
    private DeliveryStatus status;
}


<출처 : 인프런 - 자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)>

댓글남기기