[7] 고급 매핑

ttt-1-2·5일 전

교재: 자바 ORM 표준 JPA 프로그래밍 

7장에서 다룰 고급 매핑은 다음과 같다:

  • 상속 관계 패핑
  • MappedSuperClass
  • 복합 키와 식별 관계 매핑
  • 조인 테이블
  • 엔티티 하나에 여러 테이블 매핑

1. 상속 관계 매핑

객체지향에서는 상속 구조를 사용하지만 데이터베이스는 테이블 구조를 사용한다. 그래서 JPA에서는 상속 구조를 테이블로 변환하는 전략이 필요하다.

대표적으로 3가지 전략이 있다.

1.1 조인 전력

부모와 자식 각각 테이블을 만든다. 자식 테이블은 부모의 PK를 FK로 같이 사용한다. 조회 시에는 JOIN이 필요하다.

장점

  • 정규화 구조 유지
  • 데이터 중복 없음
  • 외래키 제약 사용 가능

단점

  • JOIN 많이 발생 → 성능 저하 가능
  • INSERT 시 여러 번 쿼리 실행

핵심 설정

@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")

1.2 단일 테이블 전략

모든 데이터를 하나의 테이블에 저장한다. DTYPE 컬럼으로 타입을 구분한다.

장점

  • JOIN 없음 → 조회 성능 가장 빠름
  • 구조 단순

단점

  • 사용하지 않는 컬럼은 null 허용
  • 테이블이 커질 수 있음

핵심 설정

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")

1.3 구현 클래스마다 테이블 전략

자식 엔티티마다 테이블을 만든다. 각 테이블에 부모 필드까지 모두 포함된다.

장점

  • 서브 타입별로 독립적 저장
  • NOT NULL 제약 사용 가능

단점

  • UNION 쿼리 필요 → 성능 안 좋음
  • 비추천 전략

핵심 설정

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

2. @MappedSuperClass

@MappedSuperclass는 테이블과 매핑되지 않는 부모 클래스이다. 단순히 공통 필드를 자식 엔티티에 상속하기 위한 용도이다. 데이터베이스에는 테이블이 생성되지 않는다.

  • 왜 사용하는가: 여러 엔티티에서 공통으로 사용하는 필드가 있다. (ex: id, name 같은 기본 정보. 이런 필드를 부모 클래스에 모아두고 재사용한다)
  • 구조:
    • 부모 클래스는 @MappedSuperclass로 선언한다.

    • 자식 클래스만 @Entity로 테이블과 매핑된다.

      @MappedSuperclass
      public abstract class BaseEntity {
          @Id @GeneratedValue
          private Long id;
          private String name;
      }
      
      @Entity
      public class Member extends BaseEntity {
          private String email;
      }
      
      @Entity
      public class Seller extends BaseEntity {
          private String shopName;
      }

특징:

  • 부모 클래스는 테이블이 없다
  • 자식 엔티티만 테이블이 생성된다
  • 공통 컬럼을 코드 레벨에서 재사용한다

주의점:

  • @MappedSuperclass는 엔티티가 아니다
  • em.find()나 JPQL에서 조회할 수 없다
  • 보통 abstract 클래스로 만든다

→ 정리: @MappedSuperclass는 테이블 매핑이 아닌 공통 필드 상속용 구조이다.

3. 복합 키와 식별 관계 매핑

3.1 식별 관계 vs 비식별 관계

테이블 관계는 외래 키가 기본 키에 포함되는지에 따라 나뉜다.

  • 식별 관계 (Identifying Relationship)
    • 부모의 기본 키를 자식이 내려받는다. 자식은 부모 PK를 포함해서 복합 키를 만든다.
    • PK + FK 구조
  • 비식별 관계 (Non-identifying Relationship)
    • 부모의 기본 키를 자식이 외래 키로만 사용한다.
    • 자식은 별도의 PK를 가진다.
    • 필수적 관계: FK에 NULL 불가
    • 선택적 관계: FK에 NULL 허용

3.2 복합 키: 비식별 관계 매핑

복합 키는 2개 이상의 컬럼으로 구성된 PK이다. JPA에서는 단순히 @Id 여러 개로 처리할 수 없다 →식별자 클래스를 만들어야 한다.

JPA는 두 가지 방법을 제공한다.

  • @IdClass: DB 중심, 단순
  • @EmbeddedId: 객체지향, 깔끔

@IdClass

DB 중심 방식이다. 엔티티에 여러 @Id를 직접 선언한다.

@IdClass(ParentId.class)
class Parent {
    @Id String id1;
    @Id String id2;
}

@EmbeddedId

객체지향 방식이다. 식별자 클래스를 엔티티 안에 포함한다.

@EmbeddedId
private ParentId id;

3.3 복합 키: 식별 관계 매핑

  • 식별 관계 구조: 식별 관계에서는 부모 → 자식 → 손자까지 기본 키가 계속 전파된다.

ex:

  • Parent
  • Child (PK = parent_id + child_id)
  • GrandChild (PK = parent_id + child_id + grandchild_id)

@IdClass로 식별 관계 매핑

식별 관계에서는 @Id + @ManyToOne을 같이 사용한다.

Child 엔티티 핵심

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

@Id
@Column(name = "CHILD_ID")
private String childId;

ChildId (식별자 클래스)

class ChildId {
    private String parent;   // Parent.id
    private String childId;
}

GrandChild까지 확장

@Id
@ManyToOne
@JoinColumns({
    @JoinColumn(name = "PARENT_ID"),
    @JoinColumn(name = "CHILD_ID")
})
private Child child;

@Id
@Column(name = "GRANDCHILD_ID")
private String id;

@EmbeddedId + 식별 관계

이 경우는 @MapsId 사용이 핵심이다.

Child 엔티티

@EmbeddedId
private ChildId id;

@MapsId("parentId")
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;

ChildId

@Embeddable
class ChildId {
    private String parentId;
    private String id;
}

GrandChild (EmbeddedId)

@EmbeddedId
private GrandChildId id;

@MapsId("childId")
@ManyToOne
@JoinColumns({
    @JoinColumn(name = "PARENT_ID"),
    @JoinColumn(name = "CHILD_ID")
})
private Child child;

3.4 비식별 관계로 구현

구조 변화

Before (식별 관계)

  • PK = 부모 PK 포함

After (비식별 관계)

  • PK = 독립적인 값
  • FK = 따로 존재
@Id @GeneratedValue
private Long id;

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

3.5 일대일 식별 관계

자식 PK = 부모 PK → 복합 키 필요 없음

@Entity
class BoardDetail {

    @Id
    private Long boardId;

    @MapsId
    @OneToOne
    @JoinColumn(name="BOARD_ID")
    private Board board;
}

3.6 식별 vs 비식별 관계 정리

식별 관계 단점

  • PK가 계속 증가
  • JOIN 복잡
  • SQL 복잡
  • 변경 어려움
  • 복합 키 필수

비식별 관계 장점

  • 단순한 PK
  • 유지보수 쉬움
  • 변경 유연
  • JPA 사용 편함

4. 조인 테이블

데이터베이스 테이블의 연관관계를 설계하는 방법은 크게 2가지다.

  • 조인 컬럼 사용 (외래 키)
  • 조인 테이블 사용

조인 컬럼 vs 조인 테이블

구분조인 컬럼조인 테이블
구조단순복잡
테이블 수2개3개
null 처리필요불필요
확장성낮음높음

JPA 매핑

  • 조인 컬럼
@ManyToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
  • 조인 테이블
@ManyToOne
@JoinTable(
    name = "MEMBER_LOCKER",
    joinColumns = @JoinColumn(name = "MEMBER_ID"),
    inverseJoinColumns = @JoinColumn(name = "LOCKER_ID")
)
private Locker locker;

5. 엔티티 하나에 여러 테이블 매핑

하나의 엔티티를 여러 테이블에 매핑할 수 있다.

  • @SecondaryTable
@Entity
@Table(name = "BOARD")
@SecondaryTable(
    name = "BOARD_DETAIL",
    pkJoinColumns = @PrimaryKeyJoinColumn(name = "BOARD_DETAIL_ID")
)
public class Board {

    @Id
    private Long id;

    private String title;

    @Column(table = "BOARD_DETAIL")
    private String content;
}

동작 방식:

  • BOARD → id, title

  • BOARD_DETAIL → content

  • 기본 키로 조인

  • 여러 테이블 매핑

@SecondaryTables({
    @SecondaryTable(name="BOARD_DETAIL"),
    @SecondaryTable(name="BOARD_FILE")
})
  • 단점: 항상 조인 발생, 성능 최적화 어려움
  • 권장 방식: 실무에서는: 엔티티 분리 + 연관관계 사용

0개의 댓글