0. 개요
- 상속 관계는 OOP에 존재하지만, RDB에는 존재하지 않는 개념이다.
- 그러므로 JPA는 객체 간의 상속 관계를 RDB에 적용할 수 있도록 변환하는 작업을 수행한다.
- 이번 포스팅에서는 JPA를 이용하여 상속 관계를 매핑하는 방법에 대해서 알아보자.
1. 상속 관계 매핑
- DB를 설계할 때 관계를 논리 모델과 물리 모델을 이용하여 표현한다.
→ 논리 모델: 사물의 관계를 도식화하여, 사물이 서로 어떻게 interact 하는지 표현한다.
→ 물리 모델: 논리 모델을 기반하여 ERD로 표현한다.
a) 논리 모델 구현 - 슈퍼 타입, 서브 타입
- 상속 관계를 RDB로 구현할 때 우선 논리 모델을 구현한다.
- 이때 논리 모델의 슈퍼/서브 타입을 사용하여 객체의 상속 관계를 유사하게 표현할 수 있다.
- 다음 다이어그램을 보자.
- 위의 다이어 그램은 논리 모델을 통해 슈퍼/서브 타입을 표현한 것이다.
- 위의 논리 모델을 기반으로 객체 간의 관계를 표현하면 다음과 같다.
- 이제 생성된 두 논리 모델을 기반으로 물리 모델을 구현할 수 있다.
b) 물리 모델 구현 방법
- 논리 모델을 물리 모델로 구현하는 3가지 방법(전략)이 있다.
1. Join 테이블 전략 - FK를 이용한 테이블로 변환하는 방법
2. 단일 테이블 전략 - 하나의 통합 테이블로 변환하는 방법
3. 구현 클래스 테이블 전략 - 서브 타입을 테이블로 변환하는 방법
- 각 방식에 대해서 알아보자.
c) Join 테이블 전략
- Join 테이블 전략은 FK를 이용한 Join을 통한 데이터 조회하는 방식을 사용한다.
- 물리 모델 구현 전략 중 가장 정규화된 테이블 구조를 구축한다.
- 다음 물리 모델을 살펴보자.
- 위의 ERD는 Join 전략으로 구현한 물리 모델이다.
- ITEM 테이블에는 모든 물품을 종류에 상관없이 관리한다.
- 각 물품의 type에 따라 가지는 추가적인 정보를 BOOK, MOVIE, ALBUM 테이블을 통해 관리한다.
- Join 전략을 사용하는 경우, 물품을 등록할 때마다 2번의 INSERT 쿼리가 실행되어야 한다.
- 첫 번째 INSERT는 ITEM 테이블을, 두 번째 INSERT는 물품의 type에 해당하는 테이블을 대상으로 실행하기 때문이다.
d) 단일 테이블 전략
- 단일 테이블 전략은 논리 모델에 존재하는 모든 데이터를 하나의 테이블로 구현하는 방법이다.
- 다음 물리 모델을 살펴보자.
- 위의 ERD는 단일 테이블 전략으로 구현한 물리 모델이다.
- type이라는 칼럼을 이용하여 물품을 구분하고, 각 물품에 해당하는 추가적인 정보는 칼럼을 통해 구현한다.
e) 구현 클래스 테이블 전략
- 구현 클래스 테이블 전략은 논리 모델에 존재하는 각 서브 타입을 하나의 테이블로 구현하는 방법이다.
- 다음 물리 모델을 살펴보자.
- 위의 ERD는 구현 클래스 테이블 전략으로 구현한 물리 모델이다.
- 서브 타입을 하나의 테이블로 만든 것이다.
- 위의 ERD에서 알 수 있듯이, 상위 테이블(= 슈퍼 타입)은 존재하지 않는 구조다.
f) 결론
- DB를 구현하는 방식은 3가지이지만, 객체의 관점에서는 3가지 방식 모두 상속 관계 하나로 표현한다.
- DB를 어떻게 설계하더라도 객체는 상속이라는 동일한 구조를 갖는다.
- 즉, 객체의 관계는 변함없이 상속 관계다.
- JPA는 위에서 소개한 3가지 물리 모델 구현 방식을 모두 지원한다.
- 다만, DB의 설계에 따라 JPA에서 객체를 매핑하는 방식, 처리하는 방식이 달라질 뿐이다.
- 이 부분에 대해서는 개발자가 신경 쓸 필요가 없으니, JPA는 참으로 편리한 기술이다.
2. 상속 관계 매핑 - Annotation 정리
- 상속 관계를 매핑할 때에 사용되는 어노테이션은 다음과 같다.
a) @Inheritance(strategy = InheritanceType.???)
- 테이블 전략을 설정하기 위해서는 @Inheritance 어노테이션을 사용한다.
- 그리고 원하는 테이블 구현 전략을 strategy 옵션을 통해 설정한다.
- strategy 옵션에는 다음과 같은 상수로 테이블 구현 전략을 설정할 수 있다.
1. InheritanceType.JOINED - 조인 테이블 전략
2. InheritanceType.SINGLE_TABLE - 단일 테이블 전략(default)
3. InheritanceType.TABLE_PER_CLASS - 구현 클래스 테이블 전략
b) @DiscriminatorColumn
- @DiscriminatorColumn은 구분자를 위해서 사용하는 것으로, type 칼럼을 생성한다.
- @DiscriminatorColumn은 name 옵션을 통해 칼럼의 이름을 설정할 수 있다.
- @DiscriminatorColumn의 name 옵션은 기본 값으로 "DTYPE"을 사용한다.
- DTYPE 칼럼에는 상속 관계에서 하위 Entity(= 클래스)의 이름이 자동으로 입력된다.
c) @DiscriminatorValue
- 만약 DTYPE 칼럼에 입력되는 값을 따로 설정을 해야 한다면 @DiscriminatorValue를 사용한다.
- 각 Entity마다 @DiscriminatorValue 어노테이션을 사용하여 DTYPE 칼럼에 입력되는 값을 설정할 수 있다.
* 아래에서 각 테이블 전략을 구현하면서 다시 설명할 예정이니, 참고만 하도록 하자.
3. 예시 코드
- 각 테이블 전략 구현해보기에 앞서, 아래의 코드를 예시로 사용할 예정이다.
- 참고로, 아래의 예시 코드는 Getter와 Setter를 생략하였다.
- 위의 예시 코드를 보면 상속(extends)을 이용하여 객체를 작성하였다는 점이 핵심이다.
4. JOIN 테이블 전략 구현
- JPA는 단일 테이블 전략을 default 전략으로 사용한다.
- 그러므로 원하는 테이블 전략을 구현하기 위해서는 Annotation을 사용하여 설정한다.
a) 구현 방법 - InheritanceType.JOINED
- 다음 코드를 살펴보자.
- 위의 코드처럼 Item 객체에 @Inheritance을 사용하여 Join 테이블 전략을 구현한다.
- 위의 코드를 실행하면 다음과 같이 테이블을 생성한다.
b) 데이터 삽입
- Join 테이블 전략은 데이터 삽입 시 INSERT 쿼리를 2번 수행한다.
- 예시 코드의 ITEM 테이블과 물품에 해당하는 테이블 두 곳에 데이터가 삽입되어야 하기 때문이다.
- 다음 코드를 살펴보자.
- 위의 코드를 실행하면 다음과 같은 쿼리가 콘솔에 출력된다.
- 이처럼 JPA가 알아서 Item 테이블과 Moive 테이블에 INSERT 쿼리를 수행함을 확인할 수 있다.
- DB는 다음과 같은 값을 갖는다.
c) @DiscriminatorValue("???")
- 만약 DTYPE 칼럼에 입력되는 값을 따로 설정하고 싶다면 @DiscriminatorValue를 사용한다.
- 각 Entity마다 @DiscriminatorValue 어노테이션을 사용하여 DTYPE 칼럼에 입력되는 값을 설정할 수 있다.
- 다음 코드를 살펴보자.
- Movie 클래스에 @DiscriminatorValue를 사용하여 DTYPE에 입력될 값을 설정하였다.
- 예상대로라면 DTYPE 칼럼의 값으로 Item_Album이라는 값이 생성되어야 한다.
- 다음 사진을 보자.
5. 단일 테이블 구현 전략
a) 구현 방법
- JPA는 단일 테이블 전략을 default 테이블 전략으로 사용한다.
- 그러므로 기본 예시 코드를 그대로 실행하면 다음과 같이 단일 테이블을 생성한다.
b) InheritanceType.SINGLE_TABLE
- 단일 테이블 전략이 default 이므로, 객체 간의 상속 관계를 명시하면 JPA가 알아서 단일 테이블을 구현한다.
- 하지만, 이는 가독성이 떨어지므로 다음과 같이 직접 명시하는 것을 권장한다.
c) 데이터 삽입
- 다음 코드를 살펴보자.
- 위의 코드는 앞서 Join 테이블 전략을 설명할 때 사용한 코드와 동일하다.
- 나중에 다시 설명하겠지만, JPA를 사용하면 이처럼 동일한 코드로 다른 매핑을 수행할 수 있다.
- 즉, 개발자는 객체지향적 코드를 작성하는 것에만 집중할 수 있다는 말의 의미가 여기에 있다.
- 위의 코드를 실행하면 다음과 같은 쿼리문이 생성한다.
- 분명 Movie 객체를 이용하여 데이터를 입력했으나, Item 테이블에만 값이 들어가는 것을 확인할 수 있다.
- 이처럼, JPA는 동일한 코드를 테이블 구현 전략에 따라 알아서 매핑한다.
- DB에는 다음과 같이 값이 생성된다.
c) @DiscriminateColumn 자동 생성
- 단일 테이블 전략의 경우 @DiscriminatorColumn 없이도 DTYPE 칼럼을 자동으로 생성한다.
- 자동으로 생성되는 이유는 상위 테이블(= Item, 슈퍼 타입)의 유일한 데이터 구분자가 DTYPE 칼럼이기 때문이다.
- 단일 테이블에서 DTYPE 칼럼이 없다면 데이터 조회 시 어떤 객체에게 값을 할당할지 결정할 수 없다.
- 이와 같은 이유로 단일 테이블 전략은 DTYPE 칼럼의 자동 생성을 default로 설정한다.
6. 구현 클래스 테이블 전략
- 구현 클래스 테이블 전략은 실무에서 사용하지 않는 전략이다.
- 그 이유는 아래쪽에서 다시 설명하도록 하겠다.
a) 구현 방법
- 구현 클래스 테이블 전략은 서브 타입을 하나의 객체로 생성하는 것이다.
- 즉, 서브 타입을 하나의 독립적인 테이블로 생성하고 상위 테이블(ex. Item)은 사용하지 않는다.
b) InheritanceType.TABLE_PER_CLASS
- 앞서 설명한 다른 전략과 동일하게 @Inheritance의 strategy 옵션 값을 다음과 같이 설정한다.
- 위의 코드에서 중요한 부분은 추상 클래스로 선언한다는 것이다.
- 구현 클래스 테이블 전략은 상위 테이블을 사용하지 않고, 하위 테이블만 사용한다고 했다.
- 이를 구현하기 위해서, 상위 클래스 선언 시 abstract 키워드를 사용한다.
- 위의 코드를 실행하면 출력되는 쿼리문을 살펴보자.
- Item 클래스는 추상 클래스이므로, 이를 제외한 하위 클래스만 테이블로 생성된다.
- 재미있는 점은 Album, Book, Movie 객체에 부여하지 않은 id 값이 생성된다.
c) 구현 클래스 테이블 전략의 문제점
- 구현 클래스 전략은 상위 클래스를 이용한 데이터 조회 시 문제가 발생한다.
- 여기서 문제란, 비효율적인 SELECT 쿼리를 실행한다는 것이다.
- 그 이유는 다음과 같다.
- 구현 클래스 전략은 서브 타입을 사용하여 테이블을 구성한다.
- 그러므로 데이터가 독립적으로 각 테이블에서 관리된다. 즉, Join이 불가하다.
- 이와 같은 이유로 상위 객체를 사용한 조회 시, 모든 하위 객체를 탐색한다.
- 즉, Join을 할 수 없는 독립적인 테이블이므로 모든 테이블을 탐색한다.
- 설명이 어렵다. 다음 코드를 보자.
- 위의 코드를 보면 Item 객체를 이용하여 Movie 객체의 값을 테이블에서 찾으려고 한다.
- 이를 조회 작업을 수행하기 위해 작성되는 쿼리는 다음과 같다.
Hibernate:
select
item0_.id as id1_5_0_,
item0_.name as name2_5_0_,
item0_.price as price3_5_0_,
item0_.actor as actor1_9_0_,
item0_.director as director2_9_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=?
- 이처럼 JPA가 모든 테이블을 조회하는 쿼리문을 작성하는 것을 확인할 수 있다.
7. 각 테이블 전략의 장 · 단점과 적용 조건
a) Join 테이블 전략 (권장)
장점
- 테이블 전략 중 가장 정규화된 DB 구조를 갖는다.(= 가장 많이 사용되는 전략)
- 정규화된 구조이므로 데이터 저장 공간이 효율적이다.
- FK 참조 무결성 제약조건 활용이 가능하다.(= Join을 사용한 조회가 가능)
단점
- 조회 시 Join이 자주 사용되어 성능 저하의 우려가 있다.
- Join으로 인해 SELECT 쿼리가 복잡하다.
- 데이터 저장 시 INSERT 쿼리가 2번 실행된다.
적용 조건
- Join 테이블 전략은 가장 정규화된 구조를 가지므로 사용을 적극 권장한다.
- 특히, DB의 구조가 복잡하거나 테이블이 많은 경우에는 Join 테이블 전략을 사용한다.
b) 단일 테이블 전략
장점
- 단일 테이블이므로 조회 성능이 빠르다.(= Join이 필요없다.)
- 조회 시 SELECT 쿼리가 단순하다.
단점
- PK를 제외한 모든 칼럼은 Null 값을 허용해야 한다.
- 하나의 테이블에 모든 것을 저장하므로, 테이블이 커질수록 조회 성능이 오히려 느려진다.
적용 조건
- 정말 단순한 DB 구조를 가지는 경우 사용한다.
- 단일 테이블로 충분히 관리할 수 있는 용량의 데이터를 저장하는 경우 사용한다.
c) 구현 클래스 테이블 전략 (비권장)
- 해당 전략은 실무에서 사용하지 않는 전략이다.
- 구현 클래스 전략에는 관계가 성립하지도 존재하지도 않기 때문이다.
- 구현 클래스 전략은 독립된 테이블에 의해 데이터가 관리된다.
- 그러므로 상속, 참조, FK 등 관계를 형성하는 요소가 존재하지 않는 구조의 테이블이다.
- 이와 같은 이유로 해당 전략은 사용하지 않는다.
장점
- 서브 타입을 명확하게 구분하여 처리하므로 데이터 삽입과 저장 공간에서 효과적이다.
- 각 테이블이 독립적이므로 Not Null 제약 조건을 사용할 수 있다.
단점
- 여러 테이블을 함께 조회 시 성능이 떨어진다.(= UNION SQL 필요함)
적용 조건
- 사용하지 않는다.
- 사용을 권장하지 않는다.
'Back-end > JPA 개념' 카테고리의 다른 글
14. 프록시(Proxy) (0) | 2022.04.08 |
---|---|
13. @MappedSuperClass (0) | 2022.04.07 |
11. 연관 관계의 종류(일대일, 다대다) (0) | 2022.04.05 |
10. 연관 관계의 종류(일대다, 다대일) (0) | 2022.04.04 |
9. 양방향 연관 관계 사용 시 주의사항 (0) | 2022.04.01 |
댓글