JPA

7.1 상속관계 매핑

fmoths 2021. 1. 17. 12:40

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

슈퍼타입 서브타입 논리 모델을 실제 물리 모델인 테이블로 구현할 때는 3가지 방법을 선택할 수 있다.

 

- 각각의 테이블로 변환 : 각각을 모두 테이블로 만들고 조회할 때 조인을 사용한다. JPA에서는 조인 전략이라고 한다.

- 통합 테이블로 변환 : 테이블을 하나만 사용해서 통합한다. JPA에서는 단일 테이블 전략이라 한다.

- 서브타입 테이블로 변환 : 서브 타입마다 하나의 테이블을 만든다. JPA에서는 구현 클래스마다 테이블 전략이라 한다.

 

7.1.1 조인전략

조인 전략은 엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모테이블의 기본 키를 받아서 기본키 + 외래키로 사용하는 전략이다. 따라서 조회할 때 조인을 자주 사용한다. 이 전략을 사용할 때 주의할 점이 있는 데, 객체는 타입으로 구분할 수 있지만 테이블은 타입의 개념이 없다. 따라서 타입을 구분하는 컬럼을 추가해야한다.

 

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Item {
	
    @Id @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;
    
    private String name;
    private int price;
}

@Entity
@Discriminator("A")
public class Album extends Item {

	private String artist;
}

@Entity
@DiscriminatorValue("B")
public class Movie extends Item {

	private String director;
    private String actor;
}

- @Inheritance(strategy = InheritanceType.JOINED) : 상속 매핑은 부모 클래스에 @Inheritance를 사용해야 한다. 그리고 매핑 전략을 지정해야 하는데 여기서는 조인 전략을 사용하므로 InheritanceType.JOINED를 사용했다.

- @DiscriminatorColumn(name = "DTYPE") : 부모 클래스에 구분 컬럼을 지정한다. 이 컬럼으로 저장된 자식 테이블을 구분할 수 있다. 기본값이 DTYPE이므로 @DiscriminatorColumn으로 줄여 사용해도 된다.

- @DiscriminatorValue("M") : 엔티티를 구분할 때 입력한다.

 

기본값으로 자식테이블은 부모테이블의 ID컬럼명을 그대로 사용하는데, 만약 자식테이블의 기본 키 컬럼명을 변경하고 싶으면 @PrimaryKeyJoinColumn을 사용하면 된다.

 

@Entity
@DiscriminatorValue("B")
@PrimaryKeyJoinColumn(name = "BOOK_ID")
public class Book extends Item {

	private String author;
    private String isbn;
}

 

장점

- 테이블이 정규화 된다.

- 외래 키 참조 무결성 제약조건을 활용할 수 있다.

- 저장공간을 효율적으로 활용한다.

 

단점

- 조회할 때 조인이 많아서 성능이 저하될 수 있다.

- 조회 쿼리가 복잡하다.

- 데이터를 등록할 INSERT SQL을 두 번 실행한다.

 

 

7.1.2 단일 테이블 전략

단일 테이블 전략은 테이블을 하나만 사용한다. 그리고 구분 컬럼으로 어떤 자식 데이터가 저장되었는지 구분한다. 조회할 때 조인을 사용하지 않으므로 일반적으로 빠르다.

 

이 전략을 사용할 때 주의할 점은 자식엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.

 

@Entity
@Inheritance(stratgy = InheritanceType.SINGLE_TABLE)
@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("A")
public class Album extends Item {}

InheritanceType.SINGLE_TABLE로 지정하면 단일 테이블 전략을 사용한다. 테이블 하나에 모든 것을 통합하므로 구분컬럼을 필수로 사용해야한다.

 

장점

- 조인이 필요없으므로 일반적으로 조회 성능이 빠르다.

- 조회 쿼리가 단순하다.

 

단점

- 자식엔티티가 매핑한 컬럼은 모두 null을 사용해야 한다.

- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 그러므로 상황에 따라서는 조회성능이 오히려 느려질 수 있다.

 

특징

- 구분 컬럼을 꼭 사용해야 한다. 따라서 @DiscriminatorColumn을 꼭 설정해야 한다.

- @DiscriminatorValue를 지정하지 않으면 기본으로 엔티티 이름을 사용한다.

 

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

자식 엔티티 마다 테이블을 만든다. 그리고 자식 테이블에 각각 필요한 컬럼이 모두 있다.

 

@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;
}

@Entity
public class Album extends Item {}

InheritanceType.TABLE_PER_CLASS를 선택하면 구현 클래스마다 테이블 전략을 사용한다. 이 전략을 자식 엔티티마다 테이블을 만든다. 일반적으로는 추천하지 않는 전략이다.

 

장점

- 서브타입을 구분해서 처리할 때 효과적이다.

- not null 제약조건을 사용할 수 있다.

 

단점

- 여러자식 테이블을 함께 조회할 때 성능이 느리다. (SQL에 UNION을 사용해야 한다.)

- 자식 테이블을 통합해서 쿼리하기 힘들다.

 

특징

- 구분컬럼을 사용하지 않는다.

 

이 전략은 DB설계자와 ORM 전문가 둘 다 추천하지 않는 전략이다. 조인이나 단일 테이블 전략을 고려하자.