상속관계 매핑
객체에는 상속관계가 존재하지만, DB에는 상속이 존재하지 않는다.
하지만 DB에는 슈퍼- 서브타입 이라는 모델링 기법이 존재하는데, 이것이 객체의 상속관계와 유사하다.
상속관계 매핑이란, 객체의 상속, 구조와 DB의 슈퍼타입, 서브타입을 매핑하는것이다.
이렇게 슈퍼타입, 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법들이 JPA에서 세 가지가 존재한다.
1. 데이터를 정규화시켜서 각각의 테이블로 변환하는 조인 전략
비즈니스적으로 중요하고 복잡할 때 쓴다.
기본적으로 세 전략 중 이 전략을 많이 사용한다.
슈퍼타입으로 사용할 상위 엔티티 생성 후 @Inheritance(strategy = InheritanceType.JOINED) 를 클레스에 추가한다.
또한, @DiscriminatorColumn 라는 어노테이션을 추가하여 테이블상에 DTYPE 이라는것을 추가하는데,
조인된 서브타입 테이블의 이름을 저장해 데이터를 구분하는데 도움을 준다.
(name속성을 변경하여 컬럼명을 DTYPE 외에도 다양하게 사용할 수 있다. )
상속받은 엔티티 클레스에 @DiscriminatorValue(value = "M") 를 추가해주면,(@DiscriminatorValue("M") 도 가능)
엔티티 명 대신 설정한 값인 M이 들어간다.
이후 아래처럼 하위 엔티티에서 상위 엔티티를 상속받으면 된다.
결론적으로 아래 코드에서 변경점 하나 없이 @ Inheritance의 stategy 속성 변경만으로 테이블 구조가 달라진다.
package com.example.jpa.domain;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Setter
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public class Item {
@Id @GeneratedValue
@Column(name = "item_id")
private Long id;
private String name;
private int price;
private int stockQuantity;
@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<>();
}
package com.example.jpa.domain;
import jakarta.persistence.Entity;
@Entity
public class Book extends Item{
private String author;
private String isbn;
}
package com.example.jpa.domain;
import jakarta.persistence.Entity;
@Entity
public class Album extends Item{
private String artist;
}
package com.example.jpa.domain;
import jakarta.persistence.Entity;
@Entity
public class Movie extends Item{
private String director;
private String actor;
}
상위 테이블과 하위 테이블 모두를 접근해야하므로 쿼리가 2번 나간다는 특징이 있다.
- 장점
- 테이블 정규화
- 외래 키 참조 무결성 제약조건 활용 가능
- 저장공간 효율화
- 단점
- 조회 시 조인을 많이 사용, 성능 저하
- 조회 쿼리가 복잡함
- 데이터 저장 시 INSERT SQL 2번 호출
가장 정석적인 전략이다.
2. 여러 테이블을 하나라 통합하는 단일 테이블 전략
논리 모델을 한 테이블로 구성하는 전략이다.
데이터도 얼마 안되고 너무 단순하고 확장할 일도 없을때 쓰면 좋다고 한다.
위에 상술한 @Inheritance 속성을 InheritanceType.SINGLE_TYPE으로 변경하면 된다.
Hibernate:
create table item (
price integer not null,
stock_quantity integer not null,
item_id bigint not null,
dtype varchar(31) not null,
actor varchar(255),
artist varchar(255),
author varchar(255),
director varchar(255),
isbn varchar(255),
name varchar(255),
primary key (item_id)
) engine=InnoDB
위 코드들을 보면, 각자의 세부 엔티티들은 상위 엔티티인 item을 확장했고,
그 item 테이블 쿼리를 보면 테이블에 세부 엔티티가 전부 들어간것을 볼수 있다.
- 장점
- 조인이 필요없으므로 일반적인 조회 성능이 빠름
- 조회 쿼리가 단순함
- 단점
- 자식 엔티티가 매핑한 컬럼은 모두 null 허용
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있는 상황에 따라서 조회 성능이 느려질 수 있다.
3. 슈퍼타입으로 모으지 않고 서브타입에 각각 속성들을 포함시키는 구현 클래스마다 전략
@Inheritance 속성을 InheritanceType.TABLE_PER_CLASS 로 변경하면 된다.
상위 테이블을 없애고 각각의 하부 테이블에 상위테이블 값들을 넣는 방식이다.
참고로, 이 전략에서는 @DiscriminatorColumn 이 적용되지 않는다.
상위 테이블인 ITEM이 사라졌기 때문에, 구분할 필요가 없기 때문이다.
이 전략은 단순하게 값을 넣고 뺄 때는 딱 좋은데, 상위타입으로 엔티티를 find하게되면 문제가 생긴다.
타입을 구분할 수 없기 때문이다.
Item findItem = em.find(Item.class, movie.getId());
log.info("findItem={}",findItem);
조회시 쿼리가 너무 복잡해진다.
Hibernate:
select
i1_0.id,
i1_0.clazz_,
i1_0.name,
i1_0.price,
i1_0.stock_quantity,
i1_0.artist,
i1_0.author,
i1_0.isbn,
i1_0.actor,
i1_0.director
from
(select
price,
stock_quantity,
id,
artist,
name,
null as author,
null as isbn,
null as actor,
null as director,
1 as clazz_
from
album
union
all select
price,
stock_quantity,
id,
null as artist,
name,
author,
isbn,
null as actor,
null as director,
2 as clazz_
from
book
union
all select
price,
stock_quantity,
id,
null as artist,
name,
null as author,
null as isbn,
actor,
director,
3 as clazz_
from
movie
) i1_0
where
i1_0.id=?
- 장점
- 서브 타입을 명확하게 구분해서 처리할 때 효과적
- not null 제약조건 사용 가능
- 단점
- 여러 자식 테이블을 함께 조회 할 때 성능이 느림(UNION SQL)
- 자식 테이블을 통합해서 쿼리하기 어려움
@MappedSuperclass
테이블과는 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할을 한다.
주로 등록일, 수정일, 등록자, 수정자 같이 전체 엔티티에서 공통적으로 사용하는 정보를 모을 때 사용한다.
package com.example.jpa.domain;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter
@Setter
@MappedSuperclass
public abstract class BaseEntity {
private String createdBy;
private LocalDateTime createdDate;
private String lastModifiedBy;
private LocalDateTime lastModifiedDate;
}
------------------------------------
public class Team extends BaseEntity {...}
참고 : @Entity 클래스는 엔티티나 @MappedSuperClass로 지정한 클래스만 상속가능하다.
- 특징
- 상속관계 매핑이 아님
- 엔티티 X, 테이블과 매핑 X
- 부모 클래스를 상속받는 자식 클래스에 매핑 정보만 제공한다.
- 조회, 검색 불가(em.find(BaseEntity) 불가)
- 직접 생성해서 사용할 일이 없으므로 추상 클래스 권장
'Java > JPA' 카테고리의 다른 글
JPA - 즉시로딩과 지연로딩, CASCADE와 고아객체 (1) | 2023.10.10 |
---|---|
JPA - 프록시 (0) | 2023.10.10 |
JPA - 연관관계 (0) | 2023.10.01 |
JPA - 기본 키 매핑 : @GeneratedValue (0) | 2023.09.23 |
JPA - 플러시, 준영속 상태 (0) | 2023.09.20 |