COBI-98
은로그
COBI-98
  • 은로그 (79)
    • Back-End (1)
      • Java (5)
      • Spring (16)
      • DB (1)
      • 알고리즘 (7)
      • ETC (2)
    • 개발 일기 (0)
    • 회고 (4)
    • Project (1)
      • 협업프로젝트 (7)
      • 국비프로젝트 (2)
    • Web (2)
      • Server (2)
    • Git (2)
    • CS (0)
    • 코딩테스트 (24)
      • 백준 (17)
      • 프로그래머스 (7)
    • 우아한 테크코스 (5)

블로그 메뉴

  • ✨깃허브
  • 홈
  • 방명록

공지사항

인기 글

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
COBI-98

은로그

[JPA] 도메인 설계, 엔티티 매핑
Back-End/Spring

[JPA] 도메인 설계, 엔티티 매핑

2023. 4. 19. 05:49

요구사항 분석

기능목록

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

 

도메인 분석 설계

JPA 는 도메인 모델을 다루는 데에 있어서 Mybatis 보다 상당한 강점이 있다.

그 예시를 도메인과 테이블 분석, 엔티티설계로 순차적으로  요구사항을 적용시켜 보자.

 

회원은 여러 상품을 주문할 수 있다. 

그리고 한 번 주문할 때 여러 상품을 선택할 수 있으므로 주문과 상품은 N:M 관계다. 

하지만 이런 N:M 관계는 관계형 데이터베이스는 물론이고 엔티티에서도 거의 사용하지 않는다.

따라서 그림처럼 주문상품이라는 엔티티를 추가해서 N:M 관계를 1:N, N:1 관계로 풀어내 가는 방식이 많이 사용된다.

 

엔티티(Entity)란

  • JPA가 관리하는 클래스
  • JPA를 사용해 테이블과 매핑할 클래스
  • public, protected 기본 생성자 필수
    • JPA의 구현체(ex: hibernate)가 지원하는 다양한 기능을 사용하기 위함
  • final, enum, interface, inner 클래스 사용 불가
    • @Entity로 매핑이 불가능하기 때문

또한, @Table 어노테이션을 이용해 매핑할 테이블 이름 지정, DB catalog 매핑, DB schema 매핑, DDL 생성 시 유니크 제약 조건 생성 등을 설정할 수 있다.
(DDL: CREATE, DROP, ALTER, TRUNCATE 같은 데이터 정의어)

 

엔티티 클래스

도메인과 엔티티의 구성을 보면 객체와 관계형 DB의 패러다임이 일치하다.

 

제약사항

JPA사용에 있어서  DB는 N:M 매핑을 할 수 없다.

  • N:M 매핑은 1:N, M:1로 변환해야 한다.

JPA가 Id 찾기 위해선 어노테이션을 통해 PK 키 매핑을 해야 한다.

  • @GenareteValue
  • PK 매핑을 DB에 위임, 키 시퀀스를 알아서 해줌
  • Int의 경우 AUTO_INCREMANT

연관 관계 매핑에서는 서로 동일한 필드를 참조하므로 연관 관계의 주인이 필요하다

  • 1:N 매핑의 경우 보통 FK를 가지고 있는 엔티티가 주인이 된다.
  • 1:1 매핑의 경우 데이터 접근이 많은 쪽이 주인이 된다.

 

예제 코드

더보기
@Entity
@Getter @Setter
public class Member {

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

    //    @NotEmpty 별도의 dto를 활용하여 설정해주는것이 정석
    private String name;
}

 

필드와 칼럼 매핑 annotation 정리

  • @Column : 컬럼 매핑
  • @Temporal : 날짜 타입 매핑 (LocalDAte 사용 시 생략 가능)
  • @Enumerated : enum 타입 매핑
    • Enumrated를 사용하면 Enum 매핑 가능
    • 기본적 Enum 타입은 ORDINAL인데 이건 숫자를 자동으로 증가 시킴
      하지만 중간에 값이 추가되거나 삭제될 시 숫자가 밀려버리는 등의 문제점 발생
    •  반드시 Enum 타입은 STRING을 사용 @Enumerated(EnumType.STRING)
  • @Lob : BLOB, CLOB 매핑
  • @Transient : 매핑 무시
  • @GeneratedValue의 속성
    • IDENTITY: DB에 위임
    • SEQUENCE: DB 시퀀스 오브젝트 사용
    • TABLE: 키 생성용 테이블 생성해 DB 시퀀스 흉내
    • AUTO: 방언에 따라 자동 지정 (default)

 

연관 관계 매핑

연관관계 매핑의 팁. 외래 키가 있는 곳을 연관관계의 주인으로 정해라.

양방향 연관 관계는 주인과 주인이 아닌 쪽으로 나눌 수 있다.
연관 관계의 주인은 외래 키를 관리하고, 주인이 아닌 쪽은 read 기능만 할 수 있도록 한다.
주인이 아닌 쪽은 mappedBy 속성을 통해 나타낸다.

일대일 (1:1) 매핑 

  • @OneToOne
  • '주 테이블에 외래 키', '대상 테이블에 외래 키' 중 선택 가능

일대다 (1:N) 매핑

  • @OneToMany
  • 객체의 구조상 반대편 테이블의 외래 키 관리
  • 되도록이면 다대일 양방향 매핑 이용

다대일 (N:1) 매핑

  • @ManyToOne
  • 외래 키가 있는 쪽이 연관 관계의 주인
  • 양쪽을 서로 참조하도록 개발

다대다 (N:M) 매핑

  • @ManyToMany
  • RDB는 정규화된 테이블 2개로 N:M 관계 표현 불가능
  • 많은 한계가 존재해 거의 사용하지 않음

 

상속 관계 매핑

관계형 DB는 상속 관계를 갖지 않는다.

슈퍼타입, 서브타입을 통해 서로 관계를 갖는다.

join 전략

  • @Inheritance(strategy = InheritanceType.JOINED)
  • 가장 이상적인 상속 구현
  • 정규화와 참조 무결성을 만족한다면 객체와 가장 비슷함
  • 테이블의 정규화
  • 저장공간 효율화
  • 비즈니스 로직적으로 중요하면 join 하여 사용
  • 조회 시 조인 많이 사용 (성능 저하 가능성)
  • 조회 쿼리의 복잡성
  • 데이터 저장 시 INSERT문 2번 호출
    (ex: ALBUM에 칼럼 추가하려면 ALBUM과 ITEM 테이블 둘 다 INSERT 해야 함)

 

단일 테이블 전략

  • @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
  • 조인이 필요 없으므로 일반적으로 조회 성능 빠름
  • 조회 쿼리 단순
  • 자식 엔티티가 매핑한 칼럼이 존재하지 않으면 null 허용
  • 서비스 규모가 크지 않을 때 때려 박는 방법
  • 단일 테이블에 모든 것을 저장해 테이블이 커짐 (상황에 따라 조회 성능 오히려 떨어질 수도)
  • (다만 이 경우는 테이블이 임계점에 다를 때 발생하는 문제로 보통은 문제가 없어서 많이 사용)

 

구현 클래스별 테이블 전략

  • @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
  • DBA, ORM 모두가 비추천
  • 서브 타입을 명확하게 구분해 처리할 때 효과적
  • not null 제약조건 사용 가능
  • 여러 자식 테이블을 함께 조회할 때 UNION 필요 (성능 저하)
  • 자식 테이블을 통합해 쿼리 하기 어려움

 

엔티티 설계 시 주의점

엔티티에는 가급적 Setter를 사용하지 말자

  • Setter가 모두 열려있다. 변경 포인트가 너무 많아서, 유지보수가 어렵다. 나중에 리펙토링으로 Setter 제거

모든 연관관계는 지연로딩으로 설정!

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

 

컬렉션은 필드에서 초기화하자.

  • null 문제에서 안전하다.
  • 하이버네이트는 엔티티를 영속화할 때, 컬랙션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로
    변경한다. 만약 getOrders()처럼 임의의 메서드에서 컬력션을 잘못 생성하면 하이버네이트 내부
    메커니즘에 문제가 발생할 수 있다. 따라서 필드레벨에서 생성하는 것이 가장 안전하고, 코드도 간결하다.

'Back-End > Spring' 카테고리의 다른 글

[MVC] DTO 역할 구분, 프로젝트 흐름도 분석  (0) 2023.05.09
[JPA] Entity를 Dto로 변환(리턴)의 중요성  (0) 2023.05.08
[JPA] JPA를 사용하는 이유와 JPA에 대하여  (0) 2023.04.18
[Spring] 테스트 코드 작성, Mock  (0) 2023.04.17
[MVC-2편] 오류 코드와 메시지 처리  (0) 2023.04.16
    'Back-End/Spring' 카테고리의 다른 글
    • [MVC] DTO 역할 구분, 프로젝트 흐름도 분석
    • [JPA] Entity를 Dto로 변환(리턴)의 중요성
    • [JPA] JPA를 사용하는 이유와 JPA에 대하여
    • [Spring] 테스트 코드 작성, Mock
    COBI-98
    COBI-98
    배운 것을 응용하기 위해 기록하는 것을 선호하며 백엔드를 공부하고 있습니다.

    티스토리툴바