상속관계 매핑
관계형 데이터베이스는 상속관계가 존재하지 않는다.
객체는 상속관계가 존재한다.
DB에 슈퍼타입과 서브타입 관계라는 모델링 기법이 객체 상속과 유사하다.
상속관계 매핑: 객체의 상속과 구조와 DB의 슈퍼타입 서브타입 관계를 매핑하는 것이다.
조인 전략
ITEM에 기본 값들을 두고, DTYPE로 상속받은 객체들을 구별하여 쓰는 방식이다.
단일 테이블 전략
한개의 테이블에 모든 컬럼을 추가하여 사용하는 방식이다.
구현 클래스마다 테이블 전략
각 클래스마다 테이블을 생성하는 방식이다.
위 방법들은 DB가 상속을 지원하지 않기 때문에, 사용하는 방식이다.
어떠한 방식으로 해도 객체는 그대로하면 된다.
JPA는 위 3가지 방식을 모두 지원한다.
JPA 기본 전략 자체는 한 테이블 안에 모든 컬럼으로 구성하는 것이 기본이므로 설정 정보를 추가해줘야한다.
가장 상위 클래스에 애노테이션으로 선택할 수 있다.
선언 방법
@Inheritance(strategy = InheritanceType.XXXXX)
JOINED : 조인 전략
SINGLE_TABLE : 단일 테이블 전략
TABLE_PER_CLASS : 구현 클래스마다 테이블 전략
JOINED : 조인 전략
@Entity @Setter @Getter
@Inheritance(strategy = InheritanceType.JOINED)
public class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
실행 쿼리
Hibernate:
select
movie0_.id as id1_2_0_,
movie0_1_.name as name2_2_0_,
movie0_1_.price as price3_2_0_,
movie0_.actor as actor1_6_0_,
movie0_.director as director2_6_0_
from
Movie movie0_
inner join
Item movie0_1_
on movie0_.id=movie0_1_.id
where
movie0_.id=?
inner join을 사용하여 movie를 가져오게 된다.
DTYPE은 어디갔나?
@Entity @Setter @Getter
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public class Item {
....
}
@DiscriminatorColumn을 사용하면 테이블에 DTYPE이 생성되고, Entity 명이 default로 설정되어있다.
타입 Movie가 ITEM에 생성된 것을 볼 수 있다.
만약에 타입별로 이름을 바꾸고 싶다면?
상속받는 클래스에 @DiscriminatorValue를 사용해서 이름을 바꿀 수 있다.
@Entity @Setter @Getter
@DiscriminatorValue(value = "M")
public class Movie extends Item{
private String director;
private String actor;
}
다음과 같이 결과가 바뀐 것을 볼 수 있다.
SINGLE_TABLE : 단일 테이블 전략
다른 것은 전부 그대로 두고, 부모 클래스에서 애노테이션을 설정해주면 된다.
@Entity @Setter @Getter
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn
public class Item {
// ...
}
실행 결과
값을 채우지 않은 것들에 대해선 자동으로 null값이 들어가게 된다.
또한 가장 큰 차이점은, SINGLE_TABLE 방식은 @DiscriminatorColumn을 적지 않아도 자동으로 DTYPE를 추가해준다.
단일테이블에선 MOVIE인지, BOOK인지 알 수 없기 때문에, 자동으로 생성해준다.
DTYPE은 운영상 항상 있는 것이 좋다.
구현클래스마다 테이블 전략
사용하면 안되는 전략
ITEM 테이블이 사라지게 되고 상속받는 테이블만 생성되게 된다.
하지만 치명적인 단점이 존재하는데,
실행 쿼리
Hibernate:
select
item0_.id as id1_2_0_,
item0_.name as name2_2_0_,
item0_.price as price3_2_0_,
item0_.actor as actor1_6_0_,
item0_.director as director2_6_0_,
item0_.artist as artist1_0_0_,
item0_.author as author1_1_0_,
item0_.isbn as isbn2_1_0_,
item0_.clazz_ as clazz_0_
from
( select
id,
name,
price,
actor,
director,
null as artist,
null as author,
null as isbn,
1 as clazz_
from
Movie
union
all select
id,
name,
price,
null as actor,
null as director,
artist,
null as author,
null as isbn,
2 as clazz_
from
Album
union
all select
id,
name,
price,
null as actor,
null as director,
null as artist,
author,
isbn,
3 as clazz_
from
Book
) item0_
where
item0_.id=?
Item id를 활용하여 조회를 하려면 전부 조회해야하기 때문에 유니온을 사용하여 모든 테이블을 뒤져보기 때문에, 위와 같은 쿼리가 나가게된다.
매우 비효율적으로 동작하므로, 본 방법은 사용하지 않는 것이 좋다.
최종 정리
조인 전략
기본 전략으로 가져가는 것이 좋다.
장점
테이블 정규화
외래 키 참조 무결성 제약조건을 활용가능하다.
저장공간이 효율화된다.
단점
조회시 조인을 많이 사용하여 성능 저하가 일어난다. (잘 맞추면 큰 상관 없이 잘 된다.)
조회 쿼리가 많이 복잡하다. (이것이 문제)
데이터 저장시 INSERT SQL이 2번 호출된다. (하지만 크게 신경 쓸 일이 아니다.)
단일테이블 전략
장점
조인이 필요 없으므로 조회 성능이 빠르다.
조회 쿼리가 단순하다.
단점
자식 엔티티가 매핑한 컬럼은 모두 null을 허용한다.
단일 테이브레 모든 것을 저장하여 테이블이 커질 수 있고 상황에 따라서는 조회 성능이 느려질 수 있다.(일반적으로는 빠름, 임계점을 넘지 않을때)
구현 클래스마다 테이블 전략
DBA와 ORM 전문가 모두 추천하지 않는 방식이다.
장점
서브 타임을 명확하게 구분해서 처리할 때 효과적이다.
not null 제약조건을 사용 가능하다.
단점
여러 자식 테이블을 함께 조회할 때 성능이 느리다.(UNION SQL)
자식 테이블을 통합해서 쿼리하기가 어렵다.
새로운 코드가 추가될 때마다 필드를 새로 추가해야한다.
조인 전략을 기본으로 두고, 정말 단순한 상황일때만 단일 테이블 전략을 사용하는 것을 추천한다!
@MappedSuperClass
상속관계와는 관련이 없다.
공통 매핑 정보가 필요할 때 사용한다. (id, name)
본 애노테이션이 붙은 클래스는 엔티티가 아니며, 테이블과 매핑하지 않는다.
부모 클래스를 상속 받는 자식 클래스에 매핑 정보만 제공한다.
조회, 검색이 불가능하다.
직접 생성해서 사용할 일이 없으므로 추상 클래스를 권장한다.
주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용한다.
로그를 남기는 클래스를 만들고, 이 클래스의 속성을 상속받게 사용해보자.
package reviewjpa.superclass;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@MappedSuperclass
@Getter @Setter
public class BaseEntity {
private String createBy;
private LocalDateTime createDate;
private String lastModifedBy;
private LocalDateTime modifiedDate;
}
이를 상속받고 JPAMain에 대충 값을 넣고 실행시켜보면,
BaseEntity를 상속받는 Member 클래스가 BaseEntity 필드를 사용할 수 있고, 값이 채워진 것을 볼 수 있다.
만약 컬럼 이름을 변경하고 싶다면, 똑같이 @Column(name="")을 사용하면 된다.
@MappedSuperclass
@Getter
@Setter
public class BaseEntity {
@Column(name = "INSERT_MEMBER")
private String createBy;
private LocalDateTime createDate;
private String lastModifedBy;
@Column(name = "UPDATE_MEMBER")
private LocalDateTime modifiedDate;
}
컬럼 이름을 변경 후 실행해보면 아래와 같이 바뀌어있는 것을 볼 수 있다.
'JPA > ORM 표준 JPA' 카테고리의 다른 글
[ORM 표준 JPA] 11. Embadded, 값 타입 (0) | 2023.10.04 |
---|---|
[ORM 표준 JPA] 10. 프록시와 N + 1 문제 FetchType.LAZY (0) | 2023.10.04 |
[ORM 표준 JPA] 8.다양한 연관관계 매핑 (0) | 2023.09.30 |
[ORM 표준 JPA] 7. 연관관계 매핑2 양방향 연관관계와 연관관계의 주인 (0) | 2023.09.23 |
[ORM 표준 JPA] 6. 연관관계 매핑 1 (0) | 2023.09.23 |