본 포스팅은 인프러의 JPA 기본편을 수강하고 정리하는 내용입니다.
상속관계 매핑
- 관계형 데이터베이스는 상속 관계X
- 슈퍼타입 서브타입 관계라는 모델링 기법이 객체 상속과 유사
- 상속관계 매핑: 객체의 상속과 구조와 DB의 슈퍼타입 서브타입 관계를 매핑
- 슈퍼타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 3가지 방법
- 각각 테이블로 변환 ->
조인 전략
- 통합 테이블로 변환 ->
단일 테이블 전략
- 서브타입 테이블로 변환 ->
구현 클래스마다 테이블 전략
- 각각 테이블로 변환 ->
주요 어노테이션
- @Inheritance(strategy=InheritanceType.XXX)
JOINED
: 조인 전략SINGLE_TABLE
: 단일 테이블 전략TABLE_PER_CLASS
: 구현 클래스마다 테이블 전략
- @DiscriminatorColumn(name=“DTYPE”)
- 뭘 조인해야되는지 아니깐 안넣어도 문제는 없다.
- 하지만 운영 입장에서 item 테이블만 select 해보면 어떤 타입인지 알 수 없기에 왠만하면 너어주는게 좋다.
- @DiscriminatorValue(“XXX”)
Note: JPA의 장점으로 테이블 전략을 바꾼다해도
@Inheritance(strategy=InheritanceType.XXX)
값만 변경해주면 된다. mybatis같은 경우엔 수정하는데 비용이 많이 들게 된다. 예를 들어, 조인 전략을 썼다가 성능이 너무 안나와 단일 테이블 전략으로 바꾼다하면 어노테이션 속성값만 바꿔주면 된다.
조인 전략
- Item이라는 테이블을 만들고, 데이터를 나누고 조인으로 데이터를 가져오는 방법
- insert는 item테이블 album테이블 각각 두 번하고, PK FK로 조인을 통해 데이터를 가져오는 방법
- ITEM테이블에 구분하는 DTYPE필드를 별도로 두어 어떤 상품 테이블에 속해있는지 지정
- JPA와 가장 유사한 모델이기도 함
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
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public class Item {
@Id
@GeneratedValue
private Long id;
private String name;
private int price;
...
}
@Entity
@DiscriminatorValue("B")
public class Book extends Item {
private String author;
private String isbn;
}
@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;
...
}
조인 전략의 장단점
- 장점
- 테이블 정규화
- 외래 키 참조 무결성 제약조건 활용가능
- ITEM_ID를 fk로 활용할 수 있다.
- 주문에서 item을 봐야될때 ITEM테이블만 join 걸면 된다. (설계가 깔끔해짐)
- 가격을 가지고 상품을 분류할떄 ITEM테이블만 보면된다.
- 저장공간 효율화(정규화가 되있기에)
- 단점
- 조회시 조인을 많이 사용, 성능 저하
- 사실 조인이라는게 잘 맞추면 성능이 더 잘 나오기때문에 사실 그리 성능이 저하되지 않는다
- 저장 공간이 효율화 되있기에 어쩌면 성능이 더 잘 나올수도 있다.
- 조회 쿼리가 복잡함
- 데이터 저장시 INSERT SQL 2번 호출
- 하지만 위의 것들은 그렇게 중요한 단점이 아니라 본다
- 조회시 조인을 많이 사용, 성능 저하
Note: 기본적으로 조인 전략이 정석이라고 보고 들어가야 한다. 조인 전략이 객체랑도 잘 맞고 정규화도 잘되고 설계입장에서 깔끔하게 떨어져 나온다.
단일 테이블 전략
- 논리 모델을 다 한 테이블로 합치는 방식
- ITEM테이블에 구분하는 DTYPE필드를 별도로 두어 어떤 상품 테이블에 속해있는지 지정
- 조인이 필요없다보니 성능상 장점은 있을 수 있음
- @DiscriminatorColumn(name = “DTYPE”) 어노테이션이 없어도 DTYPE이 필수로 생성된다.
- 그렇지 않다면 어떤 타입의 데이터인지 구분할 수 없게 된다.
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
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public class Item {
@Id
@GeneratedValue
private Long id;
private String name;
private int price;
...
}
@Entity
@DiscriminatorValue("B")
public class Book extends Item {
private String author;
private String isbn;
}
@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;
...
}
단일 테이블 전략의 장단점
- 장점
- 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
- 조회 쿼리가 단순함
- 단점
- 자식 엔티티가 매핑한 컬럼은 모두 null 허용(중요!)
- name이랑 price빼고는 모두 null을 허용해줘야됨
- 데이터 무결성 입장에서 애매한게 있음
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 상 황에 따라서 조회 성능이 오히려 느려질 수 있다.
- 자식 엔티티가 매핑한 컬럼은 모두 null 허용(중요!)
구현 클래스마다 테이블 전략
- Album, Movie, Book 구현 클래스 자체를 각각 테이블로 별도로 구성하는 방식
- 테이블끼리의 중복된 컬럼들이 가장 많게 됨
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
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item {
@Id
@GeneratedValue
private Long id;
private String name;
private int price;
...
}
@Entity
public class Book extends Item {
private String author;
private String isbn;
}
@Entity
public class Album extends Item {
private String artist;
}
@Entity
public class Movie extends Item {
private String director;
private String actor;
...
}
클래스마다의 테이블 전략은 단순하게 값을 넣고 뺄때는 좋은데 Item클래스로 조회할때 union all
로 다뒤지게 되는 단점이 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Movie movie = new Movie();
movie.setDirector("a");
movie.setActor("bb");
movie.setName("바람과 함께 사라지다");
movie.setPrice(10000);
em.persist(movie);
em.flush();
em.clear();
Item item = em.find(Item.class, movie.getId());
System.out.println("item = " + item);
tx.commit();
구현 클래스마다 테이블 전략의 장단점
이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천X.
뭘 묶어내야 시스템이란게 통합이 가능한데 뭔가 묶어내는게 없다. 예를 들어, Album, Movie, Book을 정산해야 한다하면 새로운게 추가 될 때마다 정산 쿼리를 또 작성해야 한다.
- 장점
- 서브 타입을 명확하게 구분해서 처리할 때 효과적
- not null 제약조건 사용 가능
- 단점
- 여러 자식 테이블을 함께 조회할 때 성능이 느림(UNION SQL 필요)
- 자식 테이블을 통합해서 쿼리하기 어려움
- 변경이라는 관점에서 굉장히 안 좋음. 새로운 타입이 추가될때 기존 것들을 엄청 뜯어내야함