고급 매핑

0taetae·2025년 1월 15일
post-thumbnail

📙상속 관계 매핑

객체의 상속 구조와 데이터베이스의 슈퍼타입 서브타입 관계를 매핑하는 것

슈퍼타입 서브타입(논리 모델)을 테이블(물리 모델)로 구현하는 방법에 대해 알아보자.

✏️ 조인 전략

엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아서 기본키 + 외래키로 사용하는 전략이다.

💡테이블은 타입의 개념이 없기 때문에 타입을 구분하는 칼럼을 추가해야 한다.

다음은 조인 전략을 사용한 코드이다.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)  // 상속 매핑, 조인 전략 사용
@DiscriminatorColumn(name = "DTYPE")  // DTYPE 칼럼을 구분 칼럼으로 사용
public abstract class Item {
   @Id @GeneratedValue
   @Column(name = "ITEM_ID")
   private Long id;
   
   private String name;
   private int price;
}

@Entity
@DiscriminatorValue("B")  // 구분 칼럼에 값 B가 저장 
@PrimaryKeyJoinColumn(name = "BOOK_ID") // 기본키 칼럼명을 BOOK_ID로 변경 
public class Book extends Item{
   private String author;
   private String isbn;
}
  • 관련 어노테이션

    • @Inheritance(strategy = InheritanceType.JOINED)
      • @Inheritance : 상속 매핑시, 부모 클래스에 사용
      • InheritanceType.JOINED : 매핑 전략 중 조인 전략을 사용
    • @DiscriminatorColumn(name = "DTYPE")
      • 부모 클래스에 구분 칼럼을 지정
        -> 저장된 자식 테이블을 구분
      • default 값이 DTYPE이므로 @DiscriminatorColumn으로 사용해도 된다.
    • @DiscriminatorValue("M")
      • 엔티티를 저장할 때 구분 칼럼에 입력할 값을 지정
    • @PrimaryKeyJoinColumn
      • 자식 테이블의 기본키 칼럼명을 변경
  • 조인 전략의 장점

    • 테이블이 정규화된다.
    • 외래 키 참조 무결성 제약조건을 활용할 수 있다.
    • 저장공간을 효율적으로 사용한다.
  • 조인 전략의 단점

    • 조회할 때 조인이 많이 사용되므로 성능이 저하될 수 있다.
    • 조회 쿼리가 복잡하다.
    • 데이터를 등록할 INSERT SQL을 두 번 실행한다.

✏️ 단일 테이블 전략

테이블을 하나만 사용해서 통합한다.

  • 테이블 하나에 통합하므로 구분 칼럼필수로 사용해야 한다. -> @DiscriminatorColumn 필수 설정
  • @DiscriminatorValue를 지정하지 않으면, 기본으로 엔티티 이름을 사용한다.

💡자식 엔티티가 매핑한 칼럼은 모두 null을 허용해야 한다.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) // 단일 테이블 전략 사용 
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item{
   @Id @GeneratedValue
   @Column(name = "ITEM_ID")
   private Long id;
   
   private String name;
   private int price;
}
  • 단일 테이블 전략의 장점

    • 조인이 필요없으므로 일반적으로 조회 성능이 빠르다.
    • 조인 쿼리가 단순하다.
  • 단일 테이블 전략의 단점

    • 자식 엔티티가 매핑한 칼럼은 모두 null을 허용해야 한다.
    • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. -> 조회 성능 저하

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

서브 타입마다 하나의 테이블을 만든다.

  • 구분 칼럼을 사용하지 않는다.
@Entity
@Inheritance(strategy = InheritanceType. TABLE_PER_CLASS) // 구현 클래스마다 테이블 전략 사용 
public abstract class Item{
   @Id @GeneratedValue
   @Column(name = "ITEM_ID")
   private Long id;
   
   private String name;
   private int price;
}
  • 구현 클래스마다 테이블 전략의 장점

    • 서브 타입을 구분해서 처리할 때 효과적이다.
    • not null 제약조건을 사용할 수 있다.
  • 구현 클래스마다 테이블 전략의 단점

    • 여러 가지 테이블을 함께 조회할 때 성능이 느리다.
    • 자식 테이블을 통합해서 쿼리하기 어렵다.

📙@MappedSuperclass

부모 클래스는 테이블과 매핑하지 않고, 부모 클래스를 상속 받는 자식 클래스에게 매핑 정보만 제공하고 싶을 때 사용한다.
즉 실제 테이블과 매핑되지 않으며, 단순히 매핑 정보를 상속할 목적으로만 사용된다.

@MappedSuperclass // 주로 사용하는 공통 매핑 정보를 정의 
public abstract class BaseEntity{
   @Id @GeneratedValue
   private Long id;
   private String name;
}

@Entity
public class Memeber extends BaseEntity{ // 매핑 정보를 상속받음
   private String email;
}
  • @MappedSuperclass로 지정한 클래스는 엔티티가 아니므로 em.find()나 JPQL에서 사용할 수 없다.
  • 여러 엔티티에서 공통으로 사용하는 속성을 효과적으로 사용할 수 있다.

💡@AttributeOverrides, @AttributeOverride : 매핑 정보를 재정의
@AssociationOverrides, @AssociationOverride : 연관관계를 재정의

📙복합 키와 식별 관계 매핑

✏️식별 관계와 비식별 관계

식별 관계와 비식별 관계는 외래 키가 기본 키에 포함되는지 여부에 따라 구분한다.

두 관계에 대해 알아보자.

식별 관계

부모 테이블의 기본키를 내려받아서 자식 테이블의 기본키 + 외래키로 사용한다.

비식별 관계

부모 테이블의 기본키를 받아서 자식 테이블의 외래키로만 사용한다.

비식별 관계는 외래키에 NULL을 허용하는지에 따라 구분한다.
1. 필수적 비식별 관계 : 외래키에 NULL을 허용하지 않는다.
2. 선택적 비식별 관계 : 외래키에 NULLL을 허용한다.

✏️복합 키 : 비식별 관계 매핑

다음은 복합키를 지원하기 위한 두가지 방법이다.

  1. @IdClass

    • 식별자 클래스를 지정할 때 사용한다.
    • 식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야 한다.
    • Serializable 인터페이스를 구현해야 한다.
    • equals, hashCode를 구현해야 한다.
    • 기본 생성자가 있어야 한다.
    • 식별자 클래스는 public이어야 한다.
    @Entity
    @IdClass(ParentId.class)  // ParentId 클래스를 식별자 클래스로 지정 
    public class Parent{
       @Id
       @Column(name = "PARENT_ID1")
       private String id1;
    }
  2. @EmbeddedId

    • @EmbeddedId를 적용한 식별자 클래스는 식별자 클래스에 기본 키를 직접 매핑한다.
    • Serializable 인터페이스를 구현해야 한다.
    • equals, hashCode를 구현해야 한다.
    • 기본 생성자가 있어야 한다.
    • 식별자 클래스는 public이어야 한다.
    @Entity
    public class Parent{
       @EmbeddedId
       private ParentId id;
       
       private String name;
    }

equals(), hashCode()

  • JPA는 영속성 컨텍스트에 엔티티를 보관할 때 엔티티의 식별자를 키로 사용한다. 식별자를 구분하기 위해 equals와 hashCode를 사용해서 동등성 비교를 한다.
    식별자 필드가 2개 이상이면 별도의 식별자 클래스를 만들고 equals와 hashCode를 구현해야 한다.
    -> 식별자 객체의 동등성이 지켜지지 않으면 영속성 컨텍스트가 엔티티를 관리하는 데 문제가 발생한다.
  • 복합키는 equals()와 hashCode()를 필수로 구현해야 한다.

✏️복합 키 : 식별 관계 매핑

식별 관계에서 자식 테이블은 부모 테이블의 기본 키를 포함해서 복합 키를 구성해야 하므로 @IdClass나 @EmbeddedId를 사용해서 식별자를 매핑해야 한다.

  1. @IdClass

    • 식별자 매핑인 @Id와 연관관계 매핑인 @ManyToOne을 같이 사용한다.
  2. @EmbeddedId

    • @MapsId와 @ManyToOne를 사용한다.
    • @MapsId
      • 식별 관계로 사용할 연관관계의 속성에 사용
      • 외래키와 매핑한 연관관계를 기본 키에도 매핑한다.
      • 속성값은 @EmbeddedId를 사용한 식별자 클래스의 기본키 필드를 지정하면 된다.

✏️일대일 식별 관계

  • 식별 관계는 자식 테이블의 기본키 값으로 부모 테이블의 기본키 값만 사용한다.
  • 부모 테이블의 기본키가 복합키가 아니면 자식 테이블의 기본키는 복합키로 구성하지 않아도 된다.

✏️식별, 비식별 관계의 장단점

  1. 식별 관계

    • 부모 테이블의 기본 키를 자식 테이블로 전파하면서 자식 테이블의 기본키 칼럼 증가
      -> 조인할 때, SQL이 복잡해지고 기본키 인덱스가 불필요하게 증가
    • 2개 이상의 칼럼을 합해서 복합 기본키를 만들어야 하는 경우가 많다.
    • 기본키로 비즈니스 의미가 있는 자연 키 칼럼을 조합하는 경우가 많다.
      -> 비즈니스 요구사항의 변화로 인해 식별 관계의 자연키 칼럼들이 자식에 손자까지 전파되면 변경이 어렵다.
    • 부모 테이블의 기본키를 자식 테이블의 기본키로 사용하므로 테이블 구조가 유연하지 못하다.
  2. 비식별 관계

    • 비식별 관계의 기본키는 비즈니스와 전혀 관계없는 대리키를 주로 사용한다.
    • 대리키를 생성하기 위해 @GenerateValue 등의 방법을 제공하여 편리성이 향상된다.

💡위와 같은 특징들로 식별 관계보다 비식별 관계를 선호한다.

📙조인 테이블

✏️조인 칼럼 사용 vs 조인 테이블 사용

  1. 조인 칼럼 사용

    • 외래키 칼럼(조인 칼럼)을 사용하여 테이블 간의 관계를 관리한다.
    • 단순히 외래키 칼럼만 추가해서 연관관계를 맺는다.
    • 객체와 테이블을 매핑할 때 @JoinColumn으로 매핑한다.
  2. 조인 테이블 사용

    • 연관관계를 관리하는 조인 테이블을 추가하고, 두 테이블의 외래키를 가지고 연관관계를 관리한다.
    • 객체와 테이블을 매핑할 때 @JoinTable으로 매핑한다.
    • @JoinTable의 속성
      • name : 매핑할 조인 테이블 이름
      • joinColumn : 현재 엔티티를 참조하는 외래 키
      • inverseJoinColumns : 반대방향 엔티티를 참조하는 외래키

✏️다양한 연관관계의 조인 테이블

  1. 일대일 조인 테이블

    • 조인 테이블의 외래키 칼럼에 각각에 총 2개의 유니크 제약조건을 걸어야 한다.
  2. 일대다 조인 테이블

    • 조인 테이블의 칼럼 중 다와 관련된 칼럼에 유니크 제약조건을 걸어야 한다.
  3. 다대다 조인 테이블

    • 조인 테이블의 두 칼럼을 합해서 하나의 복합 유니크 제약조건을 걸어야 한다.

💡조인 테이블에 칼럼을 추가하면 @JoinTable 전략을 사용할 수 없다. 새로운 엔티티를 만들어서 조인 테이블과 매핑해야 한다.

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

@SecondaryTable을 사용하여 한 엔티티에 여러 테이블을 매핑할 수 있다. 다음은 이 어노테이션을 사용한 예제이다.

@Entity
@Table(name = "BOARD")  // BOARD 테이블과 매핑
@SecondaryTable(name = "BOARD_DETAIL", pkJoinColumns = @PrimaryKeyJoinColmn(name = "BOARD_DETAIL_ID"))  // BOARD_DETAIL 테이블을 추가로 매핑
public class Board{
   @Id @GeneratedValue
   @Column(name = "BOARD_ID")
   private Long id;
   
   private String title;
   
   @Column(table = "BOARD_DETAIL")
   private String content;
}
  • @SecondaryTable의 속성
    • @SecondaryTable.name : 매핑할 다른 테이블의 이름
    • @SecondaryTable.pkJoinColumns : 매핑할 다른 테이블의 기본 키 칼럼 속성

📙정리

상속 관계 매핑

  • 조인 전략
  • 단일 테이블 전략
  • 구현 클래스마다 테이블 전략

@MappedSuperclass

  • 부모 클래스는 테이블과 매핑하지 않고, 부모 클래스를 상속 받는 자식 클래스에게 매핑 정보만 제공

조인 테이블

  • 연관관계를 관리하는 조인 테이블을 추가하고, 두 테이블의 외래키를 가지고 연관관계를 관리

0개의 댓글