상속 관계 매핑
관계형 데이터베이스는 객체지향 언어에서 다루는 상속이라는 개념이 없다. 대신 슈퍼타입 서브타입 관계 (Super-Type Sub-Type Relationship) 라는 모델링 기법이 객체의 상속 개념과 가장 유사하다. ORM에서 이야기하는 상속 관계 매핑은 객체의 상속 구조와 데이터베이스의 슈퍼타입 서브타입 관계를 매핑하는 것이다.
슈퍼타입 서브타입 논리 모델을 실제 물리 모델인 테이블로 구현할 때는 3가지 방법을 선택할 수 있다.
- 각각의 테이블로 변환 @Inheritance(strategy=IngeritanceType.JOINED)
- 모두 테이블로 만들고 조회할 때 조인을 사용한다. (JPA에서는 조인 전략이라 한다.)
- 통합 테이블로 변환 @Inheritance(strategy=IngeritanceType.SINGLE_TABLE)
- 모든 칼럼을 하나의 테이블로 구성한다. (JPA에서는 단일 테이블 전략이라 한다.)
- 서브타입 테이블로 변환 @Inheritance(strategy=IngeritanceType.TABLE_PER_CLASS)
- 서브 타입마다 하나의 테이블을 만든다. (JPA에서는 구현 클래스마다 테이블 전략이라 한다.)
예제와 함께 각각의 방법을 알아보자.
조인 전략
조인 전략은 엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아서 기본 키 + 외래 키로 사용하는 전략이다.
특징
- 조회할 때 조인을 자주 사용한다.
- 부모 테이블의 기본 키를 받아서 기본 키 + 외래 키를 사용하는 전략이기 때문에 조회할 때 조인을 자주 사용한다.
- 타입을 구분하는 컬럼을 추가한다.
- 객체는 타입으로 구분할 수 있지만 테이블은 타입의 개념이 없기 때문에 타입 구분을 위한 칼럼을 추가한다.
장점
- 테이블이 정규화된다.
- 외래 키 참조 무결성 제약조건을 활용할 수 있다.
- 저장 공간을 효율적으로 사용한다.
단점
- 조회할 때 조인이 많이 사용되므로 성능이 저하될 수 있다.
- 조회 쿼리가 복잡하다.
- 데이터를 등록할 INSERT SQL을 두 번 실행한다.
예제
ITEM
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public class Item {
@Id @GeneratedValue
@Column(name = "item_id")
private Long id;
private String name;
private int price;
// Getter and Setter
}
ALBUM
@Entity
@DiscriminatorValue("A")
public class Album extends Item{
private String artist;
}
BOOK
@Entity
@DiscriminatorValue("B")
@PrimaryKeyJoinColumn(name = "book_id")
public class Book extends Item{
private String author;
private String isbn;
}
예제 코드에서 사용된 주요 어노테이션을 알아보자.
- @Inheritance(strategy = InherianceType.JOINED)
- 상속 매핑은 부모 클래스에 @Inheritance를 사용해야 한다. 그리고 매핑 전략을 지정한다. 여기서는 조인 전략을 사용하므로 strategy 속성으로 InheritanceType.JOINED를 설정했다.
- @DiscriminatorColumn(name = "DTYPE")
- 부모 클래스에 구분 컬럼을 지정한다. 이 컬럼으로 저장된 자식 테이블을 구분할 수 있다. (기본값은 DTYPE이다.)
- @Disciminator("M")
- 엔티티를 저장할 때 구분 컬럼에 입력할 값을 지정한다. 만약 Movie 엔티티를 저장하면 구분 컬럼인 DTYPE에 M이 저장된다.
- @PrimaryKeyJoinColumn(name = "book_id")
- 자식 테이블의 기본 키 컬럼명을 지정한다. Book 엔티티에 기본 키 + 외래 키의 컬럼명은 book_id로 된다.
- @Column
- JPA에서 DB Table의 Column을 Mapping 할 때 @Column Annotation을 사용한다.
단일 테이블 전략
단일 테이블 전략은 슈퍼타입 서브타입의 모든 칼럼을 하나의 테이블로 구성한다.
특징
- 구분 컬럼을 통해 저장된 타입을 구분한다.구분 컬럼(@DiscriminatorColumn)을 반드시 사용한다.
장점
- 조인이 필요 없으므로 일반적으로 조회 성능이 빠르다.
- 조회 쿼리가 단순하다.
단점
- 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. (상황에 따라 조회 성능이 느려질 수 있다.)
예제
ITEM
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public class Item {
@Id @GeneratedValue
@Column(name = "item_id")
private Long id;
private String name;
private int price;
// Getter and Setter
}
- @Inheritance(strategy = InherianceType.SINGLE_TABLE)단일 테이블 전략을 사용하므로 strategy 속성으로 InheritanceType.SINGLE_TABLE을 설정했다.
단일 테이블 전략을 사용했을 때 SQL은 아래와 같다.
Item 테이블이 만들어지고 컬럼으로 Item, Album, Book, Movie 엔티티에 작성한 속성 값이 모두 들어간다.
구현 클래스마다 테이블 전략 (권장x)
구현 클래스마다 테이블 전략은 자식 엔티티마다 테이블을 만든다. (이때 슈퍼타입을 위한 테이블은 생성되지 않는다.) 그리고 자식 테이블 각각에 필요한 컬럼이 모두 있다.
특징
- 구분 컬럼을 사용하지 않는다.
장점
- 서브 타입을 구분해서 처리할 때 효과적이다.
- not null 제약조건을 사용할 수 있다.
단점
- 여러 자식 테이블을 함께 조회할 때 성능이 느리다(SQL에 UNION을 사용해야 한다.)
- 자식 테이블을 통합해서 쿼리하기 어렵다.
예제
ITEM
@Entity
@Inheritance(strategy = InherianceType.TABLE_PER_CLASS)
@DiscriminatorColumn(name = "DTYPE")
public class Item {
@Id @GeneratedValue
@Column(name = "item_id")
private Long id;
private String name;
private int price;
// Getter and Setter
}
- @Inheritance(strategy = InherianceType.TABLE_PER_CLASS)구현 클래스마다 테이블 전략을 사용하므로 strategy 속성으로 InheritanceType.TABLE_PER_CLASS 설정했다.
Item 엔티티에 매핑되는 테이블은 생성되지 않는다. 하지만 Item 엔티티의 속성들은 자식 엔티티의 속성에 추가되어 테이블 칼럼으로 추가된다.
@MappedSuperclass
@MappedSuperclass 는 부모 클래스를 테이블과 매핑하지 않고 부모 클래스를 상속받는 자식 클래스에게 매핑 정보만 제공한다. (실제 테이블과 매핑되지 않는다.)
위 그림에 해당하는 코드와 결과를 확인해보자.
BaseEntity에는 객체들의 공통 매핑 정보를 정의했다. 그리고 상속을 통해 공통 매핑 정보를 물려받았다. 이때 BaseEntity에 해당하는 테이블은 생성되지 않고 상속받은 객체들에게 매핑 정보만 제공한다.
재정의는 어떻게하면 돼?
부모로부터 물려받은 매핑 정보를 그대로 사용할 수 있고, 재정의하여 사용할 수 있다.
특히 @Id 는 @Column(name = "") 을 통해 매핑 정보를 지정하는 경우가 흔하다.
매핑 정보를 재정의하려면 @AttributeOverrides 또는 @AttributeOverride 를 사용하고, 연관관계를 재정의하려면 @AssociationOverrides 또는 @AssociationOverride 를 사용한다.
코드를 보며 사용법을 익혀보자.
둘 이상을 재정의하려면 @AttributeOverrides 를 사용하면 된다.
특징
- 테이블과 매핑되지 않고 자식 클래스에 엔티티의 매핑 정보를 상속하기 위해 사용한다.
- @MappedSuperclass 로 지정한 클래스는 엔티티가 아니므로 em.find() 또는 JPQL에서 사용할 수 없다.
- 이 클래스를 직접 생성해서 사용할 일은 거의 없으므로 추상 클래스로 만드는 것을 권장한다.
'Develop > JPA' 카테고리의 다른 글
[JPA/기본편] 값 타입 (0) | 2024.03.20 |
---|---|
[JPA/기본편] 프록시 (0) | 2024.03.20 |
[JPA/기본편] 연관관계 매핑 (0) | 2024.03.20 |
[JPA/기본편] 연관관계 (1) | 2024.03.20 |
[JPA/기본편] 엔티티 매핑 (0) | 2024.03.20 |
댓글