1. 상속 관계 매핑

슈퍼타입 서브타입 관계

  • 각각의 테이블로 변환: 조인 전략
  • 통합 테이블로 변환: 단일 테이블 전략
  • 서브타입 테이블로 변환: 구현 클래스마다 테이블 전략

조인 전략

엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아서 기본 키 + 외래 키로 사용하는 전략
  • 조회할 때 조인을 자주 사용
  • 객체는 타입으로 구분할 수 있지만 테이블은 타입의 개념이 없음 => DTYPE 컬럼
  • 장점
    • 테이블이 정규화됨
    • 외래 키 참조 무결성 제약조건을 활용할 수 있음
    • 저장 공간을 효율적으로 사용
  • 단점
    • 조회할 때 조인이 많이 사용되므로 성능이 저하될 수 있음
    • 조회 쿼리 복잡
    • 데이터를 등록할 때 INSERT SQL 두 번 실행
  • 특징
    • JPA 표준 명세는 구분 컬럼을 사용하도록 하지만 하이버네이트를 포함한 몇몇 구현체는 구분 컬럼(@DiscriminatorColumn) 없이도 동작
  • 관련 어노테이션
    • @PrimaryKeyJoinColumn, @DiscriminatorColumn, @DiscrimicatorValue

단일 테이블 전략

테이블을 하나만 사용하고 구분 컬럼(DTYPE)으로 어떤 자식 데이터가 저장되었는지 구분
  • 자식 엔티티가 매핑한 컬럼은 모두 null 허용 해야 함
  • 장점
    • 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
    • 조회 쿼리 단순
  • 단점
    • 자식 엔티티가 매핑한 컬럼은 모두 null 허용 해야 함
    • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있음. 그러므로 사오항에 따라서는 조회 성능이 오히려 느려질 수 있음
  • 특징
    • 구분 컬럼을 곡 사용해야 함(@DiscriminatorColumn)
    • @DiscriminatorValue를 지정하지 않으면 기본으로 엔티티 이름 사용

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

자식 엔티티마다 테이블을 만들고 자식 테이블 각각에 필요한 컬럼이 모두 있음
  • 장점
    • 서브 타입을 구분해섯 처리할 때 효과적
    • not null 제약조건을 사용할 수 있음
  • 단점
    • 여러 자식 테이블을 함게 조회할 때 성능이 느림(SQL에 UNION을 사용해야 함)
    • 자식 테이블을 통합해서 쿼리하기 어려움
  • 특징
    • 구분 컬럼을 사용하지 않음

2. @MappedSuperclass

  • 단순히 매핑 정보를 상속할 목적으로만 사용됨
  • 특징
    • 테이블과 매핑되지 않고 자식 클래스에 엔티티의 매핑 정보를 상속하기 위해 사용
    • @MappedSuperclass로 지정한 클래스는 엔티티가 아니므로 em.find()나 JPQL에서 사용할 수 없음
    • 이 클래스를 직접 생성해서 사용할 일은 거의 없으므로 추상 클래스로 만드는 것 권장
  • 엔티티(@Entity)는 엔티티(@Entity)이거나 @MappedSuperclass로 지정한 클래스만 상속받을 수 있음

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

  • 식별 관계: 부모 테이블의 기본 키를 내려받아서 자식 테이블의 기본 키 + 외래 키로 사용하는 관계
  • 비식별 관계: 부모 테이블의 기본 키를 받아서 자식 테이블의 외래 키로만 사용하는 관계
    • 필수적 비식별 관계: 외래 키에 NULL 허용X
    • 선택적 비식별 관계: 외래 키에 NULL 허용

복합 키: 비식별 관계 매핑

  • JPA는 영속성 컨텍스트에 엔티티를 보관할 때 엔티티의 식별자를 키로 사용하고, 식별자를 구분하기 위해 equals와 hashCode를 사용해서 동등성 비교

@IdClass

  • 관계형 데이터베이스에 가까운 방법
@@Entity
@IdClass(ParentId.class)
public class Parent {
	@Id
    @Column(name = "PARENT_ID1")
    private String id1;
    
    @Id
    @Column(name = "PARENT_ID2")
    private String id2;
   
   private String name;
   ...
}
public class ParentId implements Serializable {
	
    private String id1;
    private String id2;
    
    public ParentId() {
    }
    
    public ParentId(String id1, String id2) {
    	this.id1 = id1;
        this.id2 = id2;
    }
    
    @Override
    public boolean equals (Object o) {...}
    
    @Override
    public int hashCode() {...}
}
  • 식별자 클래스 조건
    • 식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야 함
    • Serializable 인터페이스를 구현
    • equals, hashCode 구현
    • 기본 생성자 있어야 함
    • 식별자 클래스는 public이어야 함

@EmbeddedId

  • 객체지향에 가까운 방법
@Entity
public class Parent {
	
    @EmbeddedId
    private ParentId id;
    
    private String name;
    ...
}
@Embeddable
public class ParentId implements Serializable {
	
    @Column(name = "PARENT_ID1")
    private String id1;
    
    @Column(name = "PARENT_ID2")
    private String id2;
    
    // equals and hashCode 구현
    ...
}
  • 식별자 클래스 조건
    • @Embeddable 어노테이션
    • Serializable 인터페이스 구현
    • equals, hashCode 구현
    • 기본 생성자 있어야 함
    • 식별자 클래스는 public

복합 키와 equals(), hashCode()

  • 복합 키는 equals()와 hashCode()를 필수로 구현해야 함
    ➡️ 영속성 컨텍스트에서 식별자를 비교할 때 사용
  • 복합 키에는 @GenerateValue 사용 불가

복합 키: 식별 관계 매핑

@IdClass

  • @Id로 기본 키 매핑하면서 @ManyToOne과 @JoinColumn으로 외래 키 매핑

@EmbeddedId

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

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

  • 데이터베이스 설계 관점
    • 식별 관계는 부모 테이블의 기본 키를 자식 테이블로 전파하면서 자식 테이블의 기본 키 컬럼이 점점 늘어남
      ➡️ 조인할 때 SQL이 복잡해지고 기본 키 인덱스가 불필요하게 커질 수 있음
    • 식별 관계는 2개 이상의 컬럼을 합해서 복합 기본 키를 만들어야 하는 경우가 많음
    • 식별 관계를 사용할 때 기본 키로 비즈니스 의미가 있는 자연 키 컬럼을 조합하는 경우자 많은 반면, 비식별 관계의 기본 키는 비즈니스와 전혀 관계없는 대리 키를 주로 사용
    • 식별 관계는 부모 테이블의 기본 키를 자식 테이블의 기본 키로 사용하므로 비식별 관계보다 테이블 구조가 유연하지 못함
  • 객체 관계 매핑의 관점
    • 일대일 관계를 제외하고 식별 관계는 2개 이상의 컬럼을 묶은 복합 기본 키를 사용
    • JPA에서 복합 키는 별도의 복합 키 클래스를 만들어서 사용해야 하는 노력 필요
    • 비식별 관계의 기본 키는 주로 대리 키를 사용하느데 JPA는 @GenerateValue처럼 대리 키를 생성하기 위한 편리한 방법 제공

추천하는 방법은 비식별 관계를 사용하고 기본 키는 Long 타입의 대리 키 사용

4. 조인 테이블

  • 조인 컬럼(@JoinColumn) 사용

    • 선택적 비식별 관계는 외래 키에 null을 허용하므로 외부 조인(OUTER JOIN)을 사용해야 함
  • 조인 테이블(@JoinTable) 사용

    • 단점: 테이블을 하나 추가해야 함
    • 주로 다대다 관계를 일대다, 다대일 관계로 풀어내기 위해 사용

따라서 기본은 조인 컬럼을 사용하고 필요하다고 판단되면 조인 테이블 사용

일대일 조인 테이블

  • 일대일 관계를 만들려면 조인 테이블의 외래 키 컬럼 각각에 유니크 제약 조건을 걸어야 함
// 부모
@Entity
public class Parent {

	@Id @GeneratedValue
    @Column(name = "PARENT_ID")
    private Long id;
    
    private String name;
    
    @OneToOne
    @JoinTable(name = "PARENT_CHILD",
    	joinColuns = @JoinColumn(name = "PARENT_ID"),
        inverseColumns = @JoinColumn(name = "CHILD_ID")
    )
    private Child child;
    
    ...
}

// 자식
@Entity
public class Child {

	@Id @GeneratedValue
    @Column(name = "CHILD_ID")
    private Long id;
    
    private String name;
    
  	...
}

일대다 조인 테이블

  • 일대다 관계를 만들려면 조인 테이블의 컬럼 중 다(N)와 관련된 컬럼에 유니크 제약조건을 걸어야 함
// 부모
@Entity
public class Parent {

	@Id @GeneratedValue
    @Column(name = "PARENT_ID")
    private Long id;
    
    private String name;
    
    @OneToMany
    @JoinTable(name = "PARENT_CHILD",
    	joinColuns = @JoinColumn(name = "PARENT_ID"),
        inverseColumns = @JoinColumn(name = "CHILD_ID")
    )
    private Child child;
    
    ...
}

// 자식
@Entity
public class Child {

	@Id @GeneratedValue
    @Column(name = "CHILD_ID")
    private Long id;
    
    private String name;
    
  	...
}

다대일 조인 테이블

// 부모
@Entity
public class Parent {

	@Id @GeneratedValue
    @Column(name = "PARENT_ID")
    private Long id;
    
    private String name;
    
    @OneToMany
    private Child child;
    
    ...
}

// 자식
@Entity
public class Child {

	@Id @GeneratedValue
    @Column(name = "CHILD_ID")
    private Long id;
    
    private String name;
    
    @ManyToOne (optional = false)
    @JoinTable(name = "PARENT_CHILD",
    	joinColuns = @JoinColumn(name = "CHILD_ID"),
        inverseColumns = @JoinColumn(name = "PARENT_ID")
    )
    private Parent parent;
  	...
}

다대다 조인 테이블

  • 다대다 관계를 만들려면 조인 테이블의 두 컬럼을 합해서 하나의 복합 유니크 제약조건을 걸어야 함
// 부모
@Entity
public class Parent {

	@Id @GeneratedValue
    @Column(name = "PARENT_ID")
    private Long id;
    
    private String name;
    
    @ManyToMany
    @JoinTable(name = "PARENT_CHILD",
    	joinColuns = @JoinColumn(name = "PARENT_ID"),
        inverseColumns = @JoinColumn(name = "CHILD_ID")
    )
    private List<Child> child = new ArrayList<Child>();
    
    ...
}

// 자식
@Entity
public class Child {

	@Id @GeneratedValue
    @Column(name = "CHILD_ID")
    private Long id;
    
    private String name;
    
  	...
}
  • 조인 테이블에 컬럼을 추가하면 @JoinTable 전략을 사용할 수 없고 새로운 엔티티를 만들어서 조인 테이블과 매핑해야 함

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

  • @SecondaryTable
profile
Backend Developer👩🏻‍💻

0개의 댓글