상세 컨텐츠

본문 제목

JPA 고급 매핑 (1) (상속관계매핑, @MappedSuperclass)

기록 - 프로그래밍/Java

by wjjun 2024. 1. 21. 00:30

본문

 

JPA 매핑 방법에는 고급 매핑 방법이 4가지가 있습니다.

그 중 상속 관계 매핑에 대해 확인해보겠습니다.

 

상속 관계 매핑

관계형 데이터베이스에는 객체지향 언어의 상속 개념이 없습니다.

모델링 기법인 슈퍼타입 서브타입 관계가 상속과 유사한 의미로 존재합니다.

 

ORM에서 상속 관계 매핑은 객체의 상속 구조와 데이터베이스의 슈퍼타입 서브타입 관계를 매핑합니다.

 

슈퍼타입 서브타입 논리 모델에서 실제 물리 모델로 구현하기 위한 3가지 방법이 있습니다.

 

각각 테이블로 변환 (조인 전략)

모두 테이블로 만들고 조회할 때 조인을 사용합니다. 조인 전략을 사용합니다.

 

통합 테이블로 변환 (단일 테이블 전략)

테이블을 하나만 사용해서 통합합니다. 단일 테이블 전략을 사용합니다.

 

서브타입 테이블로 변환 (구현 클래스별 테이블 전략)

서브 타입마다 하나의 테이블을 만듭니다. 구현 클래스마다 테이블 전략을 사용합니다.

 

 

[1] 조인 전략

엔티티를 모두 각각 테이블로 만듭니다.

자식 테이블이 부모 테이블의 기본 키를 받고, 기본 키 + 외래 키로 사용하는 전략입니다. 

 

조인 주의할 점

객체는 타입으로 구분할 수 있지만 테이블은 타입이 없습니다. 그래서 타입을 구분하는 컬럼을 추가해야 합니다.

아래는 DTYPE을 사용하여 구분했습니다.

 

부모 : ITEM

자식 : ALBUM, MOVIE, BOOK

 

ITEM : TEAM_ID (PK), NAME, PRICE, DTYPE

ALBUM : ITEM_ID (PK, FK), ARTIST

MOVIE : ITEM_ID (PK, FK), DIRECTOR, ACTOR

BOOK : ITEM_ID (PK, FK), AUTHOR, ISBN

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
    @Id @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;
    
    private String name;
    private int price;
    ...
}

@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;
    ...
}

 

@inheritance(strategy = InheritanceType.JOINED)

상속 매핑은 부모 클래스에 @inheritance를 사용해야 합니다.

매핑 전략을 지정해야 하는데 여기서는 조인 전략을 사용하므로 inheritanceType.JOINED를 사용했습니다.

 

@DiscriminatorColumn(name = "DTYPE")

부모 클래스에 구분 컬럼을 지정합니다. 이 컬럼으로 저장된 자식 테이블을 구분할 수 있습니다.

 

@DiscriminatorValue("M")

엔티티를 저장할 때 구분 컬럼에 입력할 값을 지정합니다. 영화 엔티티를 저장한다면 DTYPE 값 M이 저장됩니다.

 

조인 전략 장점

테이블이 정규화 됩니다.

외래 키 참조 무결성 제약조건을 활용할 수 있습니다.

저장공간을 효율적으로 사용합니다.

 

조인 전략 단점

조회할 때 조인이 많이 사용되므로 성능이 저하될 수 있습니다.

조회 쿼리가 복잡합니다.

데이터를 등록할 때 INSERT SQL을 두번 실행합니다. 

 

 

[2] 단일 테이블 전략

테이블을 하나만 사용합니다. 구분 컬럼(DTYPE)으로 자식 데이터가 저장되었는지 구분합니다. 조회할 때 조인을 사용하지 않아 가장 빠릅니다.

 

ITEM : ITEM (PK), NAME , PRICE, ARTIST, DIRECTOR, ACTOR, AUTHOR, ISBN, DTYPE

 

주의할 점

자식 엔티티가 매핑한 컬럼은 모두 null 허용을 해야합니다.

예를 들어 BOOK 엔티티를 저장하면 ITEM 테이블의 AUTHOR, ISBN 컬럼만 사용하고 다른 엔티티와 매핑된 ARTIS, DIRECTOR, ACTOR 컬럼은 사용하지 않아 null 데이터가 입력될 것입니다.

 

@Entity
@Injeritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
    @Id @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;
    private String name;
    private int price;
    ...
}

@Entity
@DiscriminatorColumn("A")
public class Album extends Item { ... }

@Entity
@DiscriminatorColumn("M")
public class Movie extends Item { ... }

@Entity
@DiscriminatorColumn("B")
public class Book extends Item { ... }

InheritanceType.SINGLE_TABLE로 지정하면 단일 테이블 전략을 사용합니다.

테이블 하나에 모든 것을 통합하기 때문에 구분 컬럼을 필수로 사용해야 합니다.

 

단일 테이블 전략 장점 

조인이 필요 없기 때문에 조회 성능이 높습니다.

조회 쿼리가 단순합니다.

 

단일 테이블 전략 단점

자식 엔티티가 매핑한 컬럼은 모두 null 허용을 해야 합니다.

단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있습니다. 그래서 상황에 따라 조회 성능이 느려질 수 있습니다.

 

단일 테이블 전략 특징

구분 컬럼 @DiscriminatorColumn 필수로 사용해야 합니다.

@DiscriminatorValue를 지정하지 않으면 엔티티의 이름을 사용합니다. (Movie, Book, Album)

 

 

[3] 구현 클래스마다 테이블 전략

구현 클래스마다 테이블 전략은 자식 엔티티마다 테이블을 만듭니다. 그리고 자식 테이블에 필요한 컬럼들이 각각 있습니다.

 

ALBUM : ITEM_ID(PK), NAME, PRICE, ARTIST

MOVIE : ITEM_ID(PK), NAME, PRICE, DIRECTOR, ACTOR

BOOK : ITEM_ID(PK), NAME, PRICE, AUTHOR, ISBN

 

구현 클래스마다 테이블 매핑

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item {

    @Id @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;
    
    private String name;
    private int price;
    ...
}

@Entity
public class Album extends Item { ... }

@Entity
public class Movie extends Item { ... }

@Entity
public class Book extends Item { ... }

 

InheritanceType.TABLE_PER_CLASS를 선택하면 구현 클래스마다 테이블 전략을 사용합니다.

자식 엔티티마다 테이블을 만들며 권장하지 않는 전략입니다.

 

구현 클래스마다 테이블 전략 장점

서브 타입을 구분해서 처리할 때 좋습니다.

not null 제약 조건을 사용할 수 있습니다

 

구현 클래스마다 테이블 전략 단점

여러 자식 테이블을 함께 조회할 때 성능이 느립니다. (SQL UNION 사용해야 한다)

자식 테이블을 통합해서 쿼리하기 어렵습니다.

(SQL UNION : 두 개 이상의 SELECT 결과를 하나의 결과 집합으로 결합합니다.)

 

구현 클래스마다 테이블 전략 특징

구분 컬럼을 사용하지 않습니다.

데이터베이스 설계자와 ORM 전문가 모두에게 권장되지 않는 전략이며 조인이나 단일 테이블 전략이 좋습니다.

 

 

@MappedSuperclass

부모클래스는 테이블과 매핑하지 않고 부모 클래스를 상속받는 자식 클래스에게 매핑 정보만 제공하고 @MappedSUperclass를 사용하면 됩니다

 

@Entity는 실제 테이블과 매핑되지만 @MappedSuperclass는 실제 테이블과 매핑되지 않습니다.

단순하게 매핑 정보를 상속할 목적으로만 사용됩니다.

 

MEMBER : ID (PK), NAME, EMAIL

SELLER : ID (PK), NAME, SHOPNAME

 

공통 속성 존재 : ID, NAME

공통 속성 상속 :

BaseEntity : id, name

MemberEntity : email

SellerEntity : shotName

 

@MappedSuperclass
public absbtract class BaseEntity {

    @Id @GeneratedValue
    private Long id;
    private String name;
    ...
}

@Entity
public class Member extends BaseEntity {
    private String email;
    ...
}

@Entity
public class Seller extends BaseEntity {
    private String shopName;
    ...
}

BaseEntity에 공통 매핑 정보를 정의했습니다.

자식 엔티티에는 상속을 통해 BaseEntity 매핑 정보를 물려 받았습니다.

BaseEntity는 테이블과 매핑할 필요 없이 자식 엔티티에게 공통으로 사용되는 매핑 정보만 제공하면 됩니다.

매핑 정보로 @MappedSuperclass를 사용했습니다.

 

부모에게 물려받은 매핑 정보를 재정의 하는 방법은 @AttributeOverride를 사용합니다.

연관관계를 재정의 하려면 @AssociationOverride를 사용합니다.

 

@Entity

@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID"))

public class Member extends BaseEntity { ... }

부모에게 상속받은 id 속성의 컬럼명을 MEMBER_ID로 재정의했습니다.

둘 이상을 재정의 하기 위해서는 @AttributeOverrides를 사용하면 됩니다.

 

@Entity

@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID"))

@AttributeOverride(name = "name", column = @Column(name = "MEMBER_NAME"))

public class Member extends BaseEntity { ... }

 

@MappedSuperclass 특징

테이블과 매핑되지 않고 자식 클래스에 엔티티 매핑 정보를 상속하기 위해 사용합니다.

@MappedSuperclass로 지정한 클래스는 엔티티가 아니어서 em.find()나 JPQL에서 사용할 수 없습니다.

직접 생성해서 사용할 일은 거의 없어 추상 클래스로 만드는 것을 권장합니다.

 

@MappedSuperclass로 지정하면 테이블과의 관계는 전혀 없고 엔티티만 공통으로 사용하고 매핑 정보를 모아주는 역할입니다.

 

 

 

 

 

 

관련글 더보기

댓글 영역