상속관계 매핑
- 객체는 상속관계가 있지만 관계형 데이터 베이스는 상속관계가 없다.
- DB의 슈퍼타입 서브타입 관계라는 모델링 기법이 객체의 상속과 유사하다.
- 상속관계 매핑 : 객체의 상속과 구조와 DB의 슈퍼타입 서브타입 관계를 매핑
- 슈퍼타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법
- 각각 테이블로 변환 → 조인 전략
- 통합 테이블로 변환 → 단일 테이블 전략
- 서브타입 테이블로 변환 → 구현 클래스마다 테이블 전략
JPA는 어떠한 전략으로 테이블을 구성했더라도 상속관계 매핑을 지원한다.
주요 어노테이션
- @Inheritance(strategy=InheritanceType.XXX)
- JOINED : 조인 전략
- SINGLE_TABLE : 단일 테이블 전략
- TABLE_PER_CLASS : 구현 클래스마다 테이블 전략
- @DiscriminatorColumn(name="DTYPE") → default : DTYPE
DTYPE이라는 컬럼이 super class의 테이블에 생기고, DTYPE의 값은 sub class명으로 지정된다. SingleTable 전략에서는 없어도 DTYPE이 생성되기도 하는데, 그래도 운영상 써주는 것이 좋다. - @DiscriminatorValue(“XXX”) → default : class name
sub class에 엔티티 저장 시 구분 컬럼에 입력할 값을 지정한다.
테스트 엔티티 작성
@Entity
@Getter @Setter
@DiscriminatorColumn(name="DTYPE")
public abstract class Items {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
@Entity
@Getter @Setter
@DiscriminatorValue("Album")
public class Album extends Items {
private String artist;
}
@Entity
@Getter @Setter
@DiscriminatorValue("Movie")
public class Movie extends Items {
private String director;
private String actor;
}
@Entity
@Getter @Setter
@DiscriminatorValue("Book")
public class Book extends Items {
private String author;
private String isbn;
}
// ...
Movie movie = new Movie();
movie.setDirector("감독");
movie.setActor("배우");
movie.setName("영화명");
movie.setPrice(10000);
em.persist(movie);
em.flush();
em.clear();
Movie findMovie = em.find(Movie.class, movie.getId());
System.out.println("findMovie : "+findMovie);
// ...
조인 전략
각각의 테이블을 생성하고 데이터를 나누어 조인으로 데이터를 가져온다.
@Inheritance 어노테이션 옵션 InheritanceType.JOINED 적용 후 등록 및 조회
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name="DTYPE")
@Getter @Setter
public abstract class Items {
// ...
}
- 테이블 생성 시 ITEM, ALBUM, MOVIE, BOOK 테이블 모두 생성
- 데이터 저장 시 INSERT SQL 2번 호출
- 데이터 조회 시 JOIN을 사용하여 데이터를 가져온다.
장점
- 테이블 정규화
- 외래 키 참조 무결성 제약조건 활용가능
- 저장공간 호율화
단점
- 조회 시 조인을 많이 사용, 성능 저하
- 조회 쿼리가 복잡하다.
- 데이터 저장 시 INSERT SQL 2번 호출
단일 테이블 전략(SINGLE TABLE)
논리 모델을 단일 테이블로 합쳐서 생성하고 구분 컬럼을 두어 데이터를 구분한다.
@Inheritance 어노테이션 옵션 InheritanceType.SINGLE_TABLE 적용 후 등록 및 조회
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="DTYPE")
@Getter @Setter
public abstract class Items {
//...
}
- 테이블 생성 시 단일 테이블 1개만 생성
- 데이터 저장 시 INSERT SQL 1번 호출
- 데이터 조회 시 단일 테이블에서 조회하기 때문에 JOIN 쿼리 미사용
단일테이블 전략은 조인 전략과 달리 데이터를 구분할 수 있는 방법이 없기 때문에 DTYPE 컬럼이 필수로 생성된다.
@Inheritance 어노테이션 옵션만 변경했을 뿐인데 테이블 설계가 변경되었다.
개발단계에서 성능이 너무 안 나오면 SINGLE TABLE 전략으로 수정하여 코드 수정 없이 테이블 설계를 변경할 수 있다.
→ JPA의 큰 장점!
장점
- JOIN이 필요 없으므로 조회 쿼리가 단순하고 성능이 빠르다.
단점
- 자식 엔티티가 매핑한 컬럼은 모두 null 허용
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 상황에 따라 조회 성능이 오히려 느려질 수 있다.
구현 클래스마다 테이블 전략
@Inheritance 어노테이션 옵션 InheritanceType.TABLE_PER_CLASS 적용 후 등록 및 조회
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@DiscriminatorColumn(name="DTYPE")
@Getter @Setter
public abstract class Items {
//...
}
- 테이블 생성 시 서브 테이블(MOVIE, ALBUM, BOOK)만 생성
- 데이터 저장 시 INSERT SQL 1번 호출
- 데이터 조회 시 MOVIE 테이블에서만 조회하기 때문에 단순하다.
이 전략에서는 데이터를 구분을 할 필요가 없기 때문에 @DiscriminatorColumn을 사용할 필요가 없다.
이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천하지 않는다. 사용하지 말자.
장점
- 서브 타입을 명확하게 구분해서 처리할 때 효과적이다.
- not null 제약조건 사용 가능
단점
- 여러 자식 테이블을 함께 조회할 때 성능이 느리다.(UNION SQL 필요)
- 자식 테이블을 통합해서 쿼리 하기 어렵다.
정리
관계형 데이터베이스에는 상속 관계가 없지만 슈퍼타입과 서브타입 관계라는 모델링 기법이 있다. 이 기법은 객체의 상속과 유사하고 논리모델을 데이터베이스 입장에서 3가지의 방법으로 매핑할 수 있다.
JPA에서는 @Inheritance 어노테이션으로 각각의 전략에 매핑할 수 있다.
조인 전략과 단일 테이블 전략의 장단점을 고민해 보고 상황에 따라 결정해라.
기본적으로 조인 전략을 선택하는 것을 권장하지만 너무 단순할 경우 단일 테이블 전략을 선택하는 것이 좋다.
@MappedSuperclass
- 공통 매핑 정보가 필요할 때 사용(id, name)
데이터베이스는 따로 쓰지만 객체 입장에서 속성만 상속받아서 사용하고 싶을 때가 있다.
예를 들어 실무에서 모든 테이블에 생성일자, 생성자 ID, 수정일자, 수정자 ID를 넣어야 한다고 가정해 보자.
그러면 생성일자, 생성자 ID, 수정일자, 수정자 ID를 모든 엔티티에 추가해야한다. 반복되는 것은 너무 비효율적이다.
객체에서 생성일자, 생성자ID, 수정일자, 수정자ID를 슈퍼클래스에 놓고 각각의 엔티티가 상속받아서 사용한다면 모든 코드에 추가하지 않아도 될 것이다. 이것을 지원하는 것이 @MappedSuperClass이다.
@MappedSuperclass
@Getter @Setter
public abstract class BaseEntity {
private String createId;
private String createDate;
private String modifiedId;
private String modifiedDate;
}
@Entity
@Getter @Setter
public class SubEntity extends BaseEntity {
@Id @GeneratedValue
private Long Id;
private String name;
}
공통으로 묶고 싶은 필드를 BaseEntity에 정의하고 @MappedSuperClass를 추가하였다.
SubEntity에서 BaseEntity를 상속받았다.
SubEntity se = new SubEntity();
se.setCreateDate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
em.persist(se);
실행 결과 공통으로 묶은 필드의 컬럼이 생성된 것을 확인할 수 있다.
참고 :
@MappedSuperClass는 extends를 사용할 뿐 상속과는 관계가 없다.
정리
- @MappedSuperClass는 상속관계 매핑이 아니다.
- @MappedSuperClass가 정의된 클래스는 엔티티가 아니다. 테이블과 매핑되지 않는다
- 부모 클래스를 상속받는 자식클래스에 매핑 정보만 제공한다.
- 조회, 검색 불가(em.find(BaseEntity) 불가)
- 직접 생성해서 사용할 일이 없으므로 추상 클래스로 만드는 것을 권장한다.
- 테이블과 관계없고 단순히 엔티티가 공통으로 사용하는 매핑정보를 모으는 역할을 한다.
- 주로 생성일, 생성자, 수정일, 수정자 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용한다.
참고 :
@Entity 클래스는 엔티티나 @MappedSuperClass로 지정한 클래스에만 상속 가능하다.
(엔티티는 상속받을 때, @MappedSuperClass는 속성만 사용하고 싶을 때)
해당 글은 인프런의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 강의를 정리한 내용입니다.
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런
www.inflearn.com
'JPA > JPA' 카테고리의 다른 글
[JPA] 값 타입 (0) | 2023.04.22 |
---|---|
[JPA] 프록시와 연관관계 관리 (0) | 2023.04.19 |
[JPA] 다양한 연관관계 매핑 - 다대다 (0) | 2023.04.14 |
[JPA] 다양한 연관관계 매핑 - 일대일 (0) | 2023.04.14 |
[JPA] 다양한 연관관계 매핑 - 다대일, 일대다 (0) | 2023.04.14 |
댓글