본문 바로가기
Back-end/JPA 개념

12. 상속 관계 매핑

by devraphy 2022. 4. 6.

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 필요함)

 

 적용 조건

   - 사용하지 않는다.

   - 사용을 권장하지 않는다.

 

 

 

댓글