상속 관계 매핑
- 관계형 데이터 베이스에는 객체지향 언어에서 다루는 상속이라는 개념이 없다.
- 슈퍼타입 서브타입 관계라는 모델링 기법이 상속 개념과 가장 유사하다.
슈퍼타입 서브타입 논리 모델의 테이블 구현 방법 3가지
- 각각의 테이블로 변환 (조인 전략)
- 통합 테이블로 변환 (단일 테이블 전략)
- 서브타입 테이블로 변환 (테이블 전략)
조인 전략
- 엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본키를 받아서 기본 키 + 외래 키로 사용하는 전략
- 조회할 때 조인을 자주 사용
주의
- 객체는 타입으로 구분할 수 있지만 테이블은 타입의 개념이 없다.
- 구분하는 컬럼을 추가해야 한다 -> DTYPE 컬럼
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscrimnatorColumn(name = "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{
private String artist;
}
@Entity
@DiscriminatorValue("M")
public class Movie extends Item{
private String director; //감독
private String actor; //배우
}
-
- @lnheritance(strategy = InheritanceType.JOINED): 상속 매핑은 부모 클래스에 @lnheritance 를 사용해야 한다. 그리고 매핑 전략을 지정해야 하는데 여기서는 조인 전략을 사용하므로 InheritanceType.JOINED를 사용했다.
-
- @DiscriminatorColumn(name = "DTYPE"): 부모 클래스에 구분 컬럼을 지정한다. 이 컬럼으로
저장된 자식 테이블을 구분할 수 있다. 기본값이 D7YPE이므로 @DiscriminatorColumn으로 줄여사용 가능
-
- DiscriminatorValue("M"): 엔티티를 저장할 때 구분 컬럼에 입력할 값을 지정한다. 만약 영화 엔티티를 저장하면 구분 컬럼인 DTYPE에 값 M이 저장된다.
기본값으로 자식 테이블은 부모 테이블의 ID컬럼명을 그대로 사용하는데, 만약 자식 테이블의 기본키 컬럼명을 변경하고 싶으면 @PrimaryKeyJoinColumn을 사용하면 된다.
@Entity
@DiscriminatorValue("B")
@PrimaryKeyJoinColumn(name = "BOOK_ID") //ID 재정의
public class Book extends Item{
private String author; //작가
private Strin isbn; //ISBN
}
Join 전략 장점
- 테이블이 정규화 된다.
- 외래키 참조 무결성 제약조건을 활용할 수 있다.
- 저장공간을 효율적으로 사용한다.
Join 전략 단점
- 조회할 때 조인이 많이 사용되므로 성능이 저하될 수 있다.
- 조회 쿼리가 복잡하다.
- 데이터를 등록할 INSERT SQL을 두 번 실행한다.
특징
- JPA 표준 명세는 구분 컬럼을 사용
- 하이버네이트를 포함한 몇몇 구현체는 구분 컬럼(@DiscriminatorColumn) 없이도 동작한다.
관련 어노테이션
- @PrimaryKeyJoinColumn, @DiscriminatorColumn, @DiscriminatorValue
단일 테이블 전략
- 테이블을 하나만 사용하는 전략
- 구분 컬럼(DTYPE)으로 어떤 자식 데이터가 저장되었는지 구분한다.
- 조회할 때 조인을 사용하지 않으므로 일반적으로 가장 빠르다.
주의
- 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.
Book 엔티티를 저장하면 ITEM 테이블의 AUTHOR, ISBN 컬럼만 사용하고 다른 엔티티와 매핑된 ARTIST, DIRECTOR, ACTOR 컬럼은 사용하지 않으므로 null이 입력되기 때문이다
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE”)
public abstract class Item {
@Id SGeneratedValue
@Column(name = DTYPE)
private Long id;
private String name; //0|름
private int price; //가격
}
@Entity
@DiscriminatorValue("A")
public class Album extends Item { ... }
@Entity
@DiscriminatorValue("M")
public class Movie extends Item { ... }
@Entity
@DiscriminatorValue(nBn)
public class Book extends Item { ... }
- InheritanceType.SINGLE_TABLE로 지정하면 단일 테이블 전략을 사용한다.
- 테이블 하나에 모든 것을 통합하므로 구분 컬럼을 필수로 사용해야 한다.
- 단일 테이블 전략의 장단점은 하나의 테이블을 사용하는 특징과 관련 있다.
단일 테이블 전략 장점
- 조인이 필요 없으므로 일반적으로 조회 성능이 빠르다.
- 조회 쿼리가 단순하다.
단일 테이블 전략 단점
- 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 그러므로 상황에 따라서는 조회 성능이 오히려 느려질 수 있다.
특징
- 구분 컬럼을 꼭 사용해야 한다. @DiscrimnatorColumn
- @DiscrimnatorColumn을 지정하지 않으면 기본으로 엔티티 이름을 사용한다. (예 Movie, Album, Book)
구현 클래스마다 테이블 전략
- 자식 엔티티마다 테이블을 만든다.
- 자식 테이블에 각각에 필요한 컬럼이 모두 있다.
- InheritanceType.TABLE_PER_CLASS를 선택하면 구현 클래스마다 테이블 전략을 사용한다.
- 일반적으로 추천하지 않는 전략이다.
장점
- 서브 타입을 구분해서 처리할 때 효과적이다.
- not null 제약조건을 사용할 수 있다.
단점
- 여러 자식 테이블을 함께 조회할 때 성능이 느리다.(SQL 에 UNION을 사용해야 한다.)
- 자식 테이블을 통합해서 쿼리하기 어렵다.
특징
이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천하지 않는 전략이다. 조인이나 단일 테이블 전략을 고려하자.
@MappedSuperclass
- 부모 클래스는 테이블과 매핑하지 않고 자식 클래스에게 매핑 정보만 제공하고 싶으면 사용한다.
- 비유를 하자면 추상클래스와 비슷.
- @Entitiy는 실제 테이블과 매핑 되지만 @MappedSuperClass는 실제 테이블과 매핑되지 않는다.
- 단순히 매핑 정보를 상속할 목적으로만 사용된다.
회원과 판매자는 서로 관계가 없는 테이블과 엔티티다.
테이블은 그대로 두고 객체 모델의 id, name 두 공통 속성을 부모 클래스로 모으고 객체 상속 관계로 만들어보자.
@MappedSuperclass
public abstract class BaseEntity{
@Id @GeneratedValue
private Long id;
private String name;
}
@Entity
public class Member extends BaseEntity{
//ID 상속
//NAME상속
private String email;
}
@Entity
public class Seller extends BaseENtity{
//ID 상속
//NAME 상속
private String shopName;
}
- BaseEntity에는 객체들이 주로 사용하는 공통 매핑 정보를 정의 했다.
- 자식 엔티티들은 상속을 통해 BaseEntity의 매핑 정보를 물려 받았다.
- BaseEntity는 테이블과 매핑할 필요가 없고
자식 엔티티에게 공통으로 사용되는 매핑 정보만 제공하면 된다. 따라서 @MappedSuperclass를 사용했다.
부모로 물려받은 매핑 정보 재정의
- @AttributeOverride, @AttributeOverrides
부모로 물려받은 연관관계 재정의
- @AssociationOverrides, @AssociationOverride
@Entity
@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID"))
public class Member extends BaseEntity{...}
- 부모에게 상속받은 id 속성의 컬럼명을 MEMBER_ID로 재정의 했다.
- 두 개 이상 재정의 @AttributeOverrides
@Entity
@AttributeOverrides({ @AttributeOverrides(name = "id", column = @Column(name = "MEMBER_ID")),
@AttributeOverrides(name = "name", column = @Column(name = "MEMBER_NAME"))})
public class Member extneds BaseEntity {...}
@MappedSuperclass 특징 정리
- 테이블과 매핑되지 않고 자식 클래스에 엔티티의 매핑 정보를 상속하기 위해 사용
- 지정한 클래스는 엔티티가 아니므로 em.find() 나 JPQL 에서 사용할 수 없다.
- 이 클래스를 직접 생성해서 사용할 일은 거의 없으므로 추상 클래스로 만드는것을 권장한다.
- 테이블과는 관계가 없고, 엔티티가 공통으로 사용하는 매핑 정보를 모아주는 역할
- ORM 에서 진정한 상속 매핑은 이전에 학습한 객체 상속을 데이터베이스의 슈퍼타입 서브타입 관계와 매핑하는 것이다.
- 등록일자, 수정일자, 등록자, 수정같은 여러 엔티티에서 공통으로 사용하는 속성을 효과적으로 관리 할 수 있다.
엔티티(@Entity) 는 엔티티이거나 @MappedSuperclass로 지정한 클래스만 상속 받을 수 있다.