도메인 분석 설계

박민서·2023년 5월 10일
0

Jpa1

목록 보기
3/6
  • PK(primary key)

테이블 생성 최소 한개의 PK를 설정
중복이나 NULL 값은 불가.
고유 인덱스 자동 생성( 고유 인덱스 자동 생성은 데이터베이스 관리 시스템(DBMS)에서 자주 사용되는 기능 중 하나이며, 개발자가 일일이 인덱스를 생성하거나 유일한 값을 검증하는 로직을 작성할 필요 없이 효율적인 데이터 검색과 처리를 가능하게 합니다.)
PK 지정 가능한 컬럼이 여러 개 있을 경우, 많이 사용되는 간단한 컬럼 선택

  • FK(Foreign Key)

외부 식별자 키로 테이블 간의 관계를 의미 한다.
두 테이블 간의 종속이 필요한 관계면 그 접점이 되는 칼럼을 FK로 지정하여 서로 참조할 수 있도록 관계를 맺어 줍니다.

  • 엔티티(Entity)

    DB에 표현하려고 하는 유형, 무형의 객체로서 서로 구별됨

  • 1:1

    1:1 관계란 어느 엔티티 쪽에서 상대 엔티티와 반드시 단 하나의 관계를 가지는 것

  • 1:N(일대다 관계)

    한 쪽 엔티티가 관계를 맺은 엔티티 쪽의 여러 객체를 가질 수 있는 것을 의미한다.
    일반적으로 다쪽에 FK를 두어 사용한다.
    1:N 관계는 N:M 관계처럼 새로운 테이블을 만들지 않는다.

  • M:N

    N:M 관계는 관계를 가진 양쪽 엔티티 모두에서 1:N 관계를 가지는 것을 말한다.
    즉 서로가 서로를 1:N 관계로 보고 있다.
    서로의 PK를 자신의 외래키 컬럼으로 갖고 있으면 된다.
    일반적으로 N:M 관계는 두 테이블의 대표키를 컬럼으로 갖는 또 다른 테이블을 생성해서 관리한 다.

  • 테이블

    데이터베이스에서 데이터를 저장하는 데 사용되는 구조체이다. 테이블은 데이터의 행(row)과 열(column)로 이루어진 2차원 구조를 가지며, 각 열은 해당 데이터에 대한 속성(attribute)을 나타내고 각 행은 실제 데이터 레코드(record)를 나타냅니다.

  • 컬럼
    컬럼(column, 열)이란 관계형 데이터베이스 테이블에서 특정한 단순 자료형의 일련의 데이터값과 테이블에서의 각 열을 말한다. 칼럼은 열이 어떻게 구성되어야 할 지에 대한 구조를 제공한다.

  • 트랜젝션
    데이터베이스의 상태를 변화시키기 위해 수행하는 작업 단위

    원자성(Atomicity)

트랜잭션이 DB에 모두 반영되거나, 혹은 전혀 반영되지 않아야 된다.

일관성(Consistency)

트랜잭션의 작업 처리 결과는 항상 일관성 있어야 한다.

독립성(Isolation)

둘 이상의 트랜잭션이 동시에 병행 실행되고 있을 때, 어떤 트랜잭션도 다른 트랜잭션 연산에 끼어들 수 없다.

지속성(Durability)

트랜잭션이 성공적으로 완료되었으면, 결과는 영구적으로 반영되어야 한다.

요구사항 분석

도메인 모델과 테이블 설계

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

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

  • 회원 엔티티 분석

회원(Member): 이름과 임베디드 타입인 주소( Address ), 그리고 주문( orders ) 리스트를 가진다.

주문(Order): 한 번 주문시 여러 상품을 주문할 수 있으므로 주문과 주문상품( OrderItem )은 일대다 관계다. 주문은 상품을 주문한 회원과 배송 정보, 주문 날짜, 주문 상태( status )를 가지고 있다.
주문 상태는 열거형을 사용했는데 주문( ORDER ), 취소( CANCEL )을 표현할 수 있다.

주문상품(OrderItem): 주문한 상품 정보와 주문 금액( orderPrice ), 주문 수량( count ) 정보를 가지고 있다. (보통 OrderLine , LineItem 으로 많이 표현한다.)

상품(Item): 이름, 가격, 재고수량( stockQuantity )을 가지고 있다. 상품을 주문하면 재고수량이 줄어든다. 상품의 종류로는 도서, 음반, 영화가 있는데 각각은 사용하는 속성이 조금씩 다르다.

배송(Delivery): 주문시 하나의 배송 정보를 생성한다. 주문과 배송은 일대일 관계다.

카테고리(Category): 상품과 다대다 관계를 맺는다. parent , child 로 부모, 자식 카테고리를 연결한다. 근데 다대다 관계는 JPA에서 쓰면 안된다. 1:다 OR 다:1로 풀어내야 한다.

주소(Address): 값 타입(임베디드 타입)이다. 회원과 배송(Delivery)에서 사용한다.

임베디드 타입(복합 값 타입) : 새로운 값 타입을 직접 정의해서 사용할 수 있는데, JPA에서는 이것을 임베디드 타입(embedded type)이라 한다.
중요한 것은 직접 정의한 임베디드 타입도 int, String처럼 값 타입이라는 것이다.

참고: 회원 엔티티 분석 그림에서 Order와 Delivery가 단방향 관계로 잘못 그려져 있다. 양방향 관계가 맞다.

참고: 회원이 주문을 하기 때문에, 회원이 주문리스트를 가지는 것은 얼핏 보면 잘 설계한 것 같지만, 객체 세상은 실제 세계와는 다르다. 실무에서는 회원이 주문을 참조하지 않고, 주문이 회원을 참조하는 것으로
충분하다. 여기서는 일대다, 다대일의 양방향 연관관계를 설명하기 위해서 추가했다.

  • 회원 테이블 분석

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

ITEM : 싱글 테이블 전략이다. 한 테이블에 앨범, 도서, 영화 타입을 통합해서 DTYPE컬럼으로 타입을 구분한다.

ORDERS : 테이블이름이 ORDER가 아니라 ORDERS인 이유는 ORDER BY 예약어 때문에 ORDER이 잘 안먹는 경우 때문이다.

CATEGORY 테이블과 ITEM 테이블은 회원 엔티티 분석에서 다대다 관계였지만 관계형 릴레이션에선 이걸 풀어내지 못하기 때문에 중간에 CATEGORY_ITEM이라는 매핑 테이블을 두어서 1:다 다:1 관계로 나누었다.

  • 연관 관계 매핑 분석

    회원과 주문 : 일대다 , 다대일의 양방향 관계다. 따라서 연관관계의 주인을 정해야 하는데, 외래 키가 있는 주문을 연관관계의 주인으로 정하는 것이 좋다. 그러므로 Order.member 를 ORDERS.MEMBER_ID 외래 키와 매핑한다.
    일대다 관계에서는 무조건 다 쪽에 외래키를 두어야한다.

    주문상품과 주문 : 다대일 양방향 관계. 외래 키가 주문상품 쪽에 있으므로 주문상품이 연관관계의 주인이다. 그러므로 OrderItem.order를 Order_Item.ORDER_ID 외래 키와 매핑한다.

    주문상품과 상품 : 다대일 단방향관계. OrderItem은 Item으로 가는 참조 값이 존재하는데 Item에서 OrderItem으로 가는 참조값은 존재하지 않는다.

    주문과 배송 : 일대일 단방향 관계 일대일 관계는 외래 키를 어디에 두어도 상관없다. 외래 키가 존재하는 곳이 연관관계의 주인이 된다.

    카테고리와 상품 : 다대다 관계를 CATEGORY_ITEM을 매핑 테이블로 두어 다대일 일대다 관계로 풀어서 사용한다.

    엔티티 클래스 개발1

  • 예제에서는 설명을 쉽게하기 위해 엔티티 클래스에 Getter, Setter를 모두 열고, 최대한 단순하게 설계

  • 실무에서는 가급적 Getter는 열어두고, Setter는 꼭 필요한 경우에만 사용하는 것을 추천

  • 회원 엔티티


@Entity // DB테이블에 대응하는 하나의 클래스, JPA가 관리 JPA를 사용해 DB 테이블과 매핑할 클래스에 붙여줌
@Getter @Setter // getter, setter 자동 생성
public class Member {

    @Id @GeneratedValue // @Id: 기본키 설정 @GeneratedValue: 기본키 생성 전략
    @Column(name= "member_id") //PK명이 member_id이므로 컬럼명을 바꿔줌
    private long id;
    private String name;

    @Embedded // @Embedded : 값 타입을 사용하는 곳에 표시
    private Address address;

    @OneToMany(mappedBy = "member") // 일대다 관계 member에 의해 매핑된 거울이라는 뜻(읽기전용)
    private List<Order> orders = new ArrayList<>();
}
  • 주문 엔티티

@Entity
@Table(name = "orders") // 테이블 이름을 적어줘야함 예약어 때문인듯
@Getter@Setter
public class Order {
    @Id@GeneratedValue
    @Column(name = "order_id")
    private Long id;

    @ManyToOne // 다대일 관계
    @JoinColumn(name = "member_id") // 매핑을 member_id로 하겠다는 뜻(FK)
    private Member member; //Member랑 관계를 세팅해주어야 함 연관관계의 주인은 아무것도 안적어도 됨

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

    @OneToOne
    @JoinColumn(name = "delivery_id") //일대일 관계에선 FK가 어디에 있어도 상관없지만 Order을 보고 Delivery를 찾을 일이 많기 때문에 Order쪽에 두었다
    private Delivery delivery;

    private LocalDateTime orderDate;    // 주문 시간

    @Enumerated(EnumType.STRING)
    private OrderStatus status; //주문상태 [ORDER, CANCEL]
}
  • 주문 상품 엔티티


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

    private int orderPrice; // 주문 가격
    private int count; // 주문 수량
}
  • 상품 엔티티


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

}
  • 배송 엔티티

@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) // Enum타입은 스트링으로 하자
    private DeliveryStatus status; // READY, COMP 배송준비, 배송
}
  • 카테고리 엔티티

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

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

    @OneToMany(mappedBy = "parent")
    private List<Category> child = new ArrayList<>();
}
  • 주소 값 타입


@Embeddable // JPA의 내장타입 @Embeddable : 값 타입을 정의하는 곳에 사용
@Getter @Setter
public class Address {
    private String city;
    private String street;
    private String zipcode;

}

값타입은 값이 변경이 되면 안된다. 좋은 설계는 값 이 생성될 때만 값이 세이팅이되고 Setter를 제공하지 않는 것이다. Setter를 빼면 기본 생성자를 만들어 주어야한다. 하지만 public으로 생성하면 사람들이 호출을 할 수 있으므로 JPA에서는 protected Address() {} 까지는 허용해준다.

엔티티 설계시 주의점

  • 실무에서는 가급적 Setter를 사용하지 말자

모든 연관관계는 지연로딩으로 설정!! (중요)

즉시로딩( EAGER )은 예측이 어렵고, 어떤 SQL이 실행될지 추적하기 어렵다. 특히 JPQL을 실행할 때 N+1 문제가 자주 발생한다.
실무에서 모든 연관관계는 지연로딩( LAZY )으로 설정해야 한다.
연관된 엔티티를 함께 DB에서 조회해야 하면, fetch join 또는 엔티티 그래프 기능을 사용한다.

@XToOne(OneToOne, ManyToOne) 관계는 기본이 즉시로딩이므로 직접 지연로딩으로 설정해야
한다. -> 기본 패치가 EAGER이고 OneToMany는 기본 패치가 LAZY이다. 그래서 OneToMany로 되어 있는 것들은 바꾸어 줄 필요가 없고 기본 패치가 EAGER은 다 찾아서 LAZY로 봐꺼주어야 한다.

  • 컬렉션은 필드에서 초기화 하자.
    컬렉션은 필드에서 바로 초기화 하는 것이 안전하다.
    Member 클래스의
    private List orders = new ArrayList<>();
    orders 컬렉션을 가능하면 밖으로 꺼내지도 말고 (꺼내도 수정하지 않으면 된다.) 변경하면 안된다. *스프링 부트 신규 설정 (엔티티(필드) 테이블(컬럼))
  1. 카멜 케이스 언더스코어(memberPoint member_point)
  2. .(점) _(언더스코어)
  3. 대문자 소문자
  • cascade
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List orderItems = new ArrayList<>();
    OrderItems에 data를 넣어 두고 Order클래스를 저장 하면 OrderItems에도 같이 저장된다.
    data를 추가할 때도 같이 추가되고 삭제할 때도 같이 삭제 된다.

  • 연관관계 편의 메서드

    • 양방향 연관관계를 맺을 때에는 양쪽 모두 관계를 맺어주어야한다.
    • JPA 입장에서 보았을 때 연관관계의 주인(외래키) 쪽에만 관계를 맺어준다면 정상적으로 양 쪽 모두에서 조회가 가능하다.
    • 하지만 객체까지 고려한다면, 양쪽 다 관계를 맺어야한다. 즉 객체의 양방향 연관관계는 양쪽 모두 관계를 맺어주어야 순수한 객체 상태에서도 정상적으로 동작한다.

    애플리케이션 아키텍처

    계층형 구조 사용

  • 웹 계층을 위한 Controlloer

  • 핵심 비즈니스와 트랜잭션 처리를 위한 Service

  • JPA와 엔티티 매니저를 사용하여 DB에 접근하기 위한 Repository

  • 모든 계층에서 Domain(엔티티가 모여있는 계층)을 사용

profile
ㅎㅇㅌ

0개의 댓글