연관 관계 고급
참고
상속 관계 매핑
- 관계형 DB에는 상속이란 개념이 없고, 슈퍼타입-서브타입 모델링이 있다.
JPA는 RDB의 슈퍼타입-서브타입 모델링을 상속으로 구현할 수 있다.
- RDB에서 슈퍼타입-서브타입 모델링을 구현할 때 보통 세 가지 전략이 있다.
- 조인 전략
- 단일 테이블 전략
- 복수 테이블 전략
조인 전략
- 아이템을 종류별로 각각 정규화된 테이블로 나누는 전략이다.
- 메인 테이블의 PK를 서브 테이블의 PK면서 FK로 사용한다.
- @Inheritance(strategy = InheritanceType.JOINED)
- @DiscriminatorColumn으로 어느 서브 클래스를 가지는지 나타내는 컬럼을 넣어줄 수 있다.
- 서브 클래스에서는 @DiscriminatorValue로 DTYPE으로 나타낼 값을 정할 수 있다.
- id 값으로 조인하여 가져오면 되기 때문에 꼭 필수로 넣지는 않아도 된다.
- 장점
- 정규화된 테이블
- 외래키 참조 무결성 제약조건 활용 가능
- 저장 공간의 효율성
- 단점
- 조회 시에 조인이 많아 성능 저하
- 조회 쿼리가 복잡하다.
- 삽입 쿼리가 두 번씩 날아간다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
class Item {
@Id
private Long id;
// ...
}
@Entity
class Album extends Item {
// ...
}
@Entity
class Movie extends Item {
// ...
}
@Entity
class Book extends Item {
// ...
}
- 이렇게 상속으로 구현할 수 있다.
- 서브 테이블들은 수퍼 테이블의 PK를 PK로 가진다.
- JPA 코드에서는 Item의 id를 서브 클래스들이 상속받기 때문에 id를 따로 가지지 않는다.
- 위에서 구현한 엔티티들로 삽입 쿼리를 날리면,
1
2
3
4
5
6
7
8
9
10
11
12
tx.begin();
Movie movie = Movie.builder()
.title("babo movie")
.description("very good")
.build();
em.persist(movie);
tx.commit();
- Item 테이블과 Movie 테이블에 각각 한 번씩 총 두 번 insert 쿼리가 날아간 것을 볼 수 있다.
- 서브 테이블을 나타내는 DTYPE이 MOVIE로 잘 나온 것을 볼 수 있다.
- Movie를 조회하면 위와 같이 Item 테이블과 Movie 테이블을 조인하여 데이터를 가져오는 것을 볼 수 있다.
단일 테이블 전략
- 하나의 테이블에 모든 컬럼을 넣는 전략이다.
- 성능의 이점이나 단순하다는 장점이 있다.
- @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
- 어느 서브 테이블을 가지는지 구분할 수 없기 때문에, DTYPE이 필수적이다.
- @DiscriminatorColumn
- 어노테이션 안붙여도 자동으로 넣어버림
- 장점
- 조인이 없으므로, 조회가 빠르다.
- 조회 쿼리가 단순하다.
- 단점
- 서브 클래스의 컬럼들은 모두 nullable이어야 한다.
- 테이블이 점점 더 커질 우려가 있다.
- Item 테이블 하나만 만들면 된다.
- Movie, Book, Album 등의 서브 테이블에서 사용하는 컬럼 모두를 Item 테이블이 가진다.
- 위 이미지의 경우 Movie를 넣었기 때문에 Movie와 상관없는 Book과 Album의 컬럼들은 null이다.
- 딱 봐도 DTYPE이 없으면 구분이 힘들기 때문에 필수적이다.
- Jpa가 Item 클래스의 모든 서브 클래스들의 컬럼을 모아 쿼리를 날려준다.
- 하나의 테이블이기 때문에 삽입, 조회 등의 쿼리들이 단순하다.
- join 없음, 2개 이상 테이블 조회 없음
- Member 엔티티를 조회하면, DTYPE을 사용해 쿼리를 날리는 것을 볼 수 있다.
복수 테이블 전략
- 클래스 당 하나씩 테이블을 만드는 전략이다.
- 단순하다는 장점이 있다.
- @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
- 서브 테이블이 다 나뉘어져 구분이 바로 되기 때문에 DTYPE이 필요가 없다.
- 어노테이션 붙여도 안 만들어줌
- 추천되지 않는 전략
- 장점
- 서브 테이블이 명확히 나뉜다는 편리함
- 단점
- 서브 테이블을 함께 조회할 때 성능이 좋지 못하다.
- 테이블 구조 변경에 번거로움
- Movie 엔티티를 저장하고 조회하면, Movie 테이블에만 로우가 추가된걸 볼 수 있다.
1
2
3
Item item = em.find(Item.class, movieId);
- 이 전략에서 조회 시에는 주의할 점이 있다.
- 상속 관계이기 때문에 Item 클래스로 Movie를 조회할 수가 있는데
- Item은 서브 테이블이 세 가지나 있고, 저 조회 명령에선 어느 서브 테이블의 로우를 찾는지 알 수가 없다.
- 그래서 서브 테이블을 다 뒤진다.
싱글 테이블 전략 Item 조회 쿼리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
select
item0_.id as id1_2_0_,
item0_.description as descript2_2_0_,
item0_.artist as artist1_0_0_,
item0_.author as author1_1_0_,
item0_.title as title1_3_0_,
item0_.clazz_ as clazz_0_
from
( select
id,
description,
null as artist,
null as author,
null as title,
0 as clazz_
from
item
union
all select
id,
description,
artist,
null as author,
null as title,
1 as clazz_
from
album
union
all select
id,
description,
null as artist,
author,
null as title,
2 as clazz_
from
book
union
all select
id,
description,
null as artist,
null as author,
title,
3 as clazz_
from
movie
) item0_
where
item0_.id=?
- 모든 서브 테이블을 뒤져서 우주주죽 길고 복잡한 쿼리가 날라간다.
@MappedSuperclass
- 공통 매핑 정보가 있을 때 사용한다.
- 서로 다른 테이블이지만 속성만 묶어서 사용하고 싶을 때 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity
class Book {
// ...
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@Entity
class Bookshelf {
// ...
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
- 위처럼 생성 시간, 수정 시간을 모든 엔티티가 공통적으로 알고 있어야 될 때 이렇게 일일이 만들어줘야 한다.
- 이런 방식은 코드가 지저분해지므로 보기에 좋지 않다.
- 이럴 때 사용하는 것이 @MappedSuperclass 이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@MappedSuperclass
class BaseEntity {
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@Entity
class Book extends BaseEntity {
// ...
}
@Entity
class Bookshelf extends BaseEntity {
// ...
}
- @MappedSuperclass로 공통적인 부분을 묶어서 만든다.
- 다른 클래스들이 BaseEntity를 상속받으면 공통적인 컬럼을 한 번에 묶어서 만들 수 있다.
- 공통적인 컬럼을 만들기 위한 목적이다.
- 엔티티가 아님
- 테이블도 생성되지 않음
- 직접 생성할 일이 없으므로 추상 클래스로 만드는 것이 추천된다.
- BaseEntity에도 @Column 등을 적용할 수 있으며, 서브 클래스들 모두의 테이블에 적용된다.
참고로 @Entity 클래스는 @Entity 클래스나 @MappedSuperclass 클래스만 상속 받을 수 있다.
This post is licensed under CC BY 4.0 by the author.