1. 상속관계 매핑
- 객체는 상속관계가 있지만, 관계형 데이터베이스는 상속 관계가 없다.
- 비슷한 걸로 슈퍼타입과 서브타입 관계라는 모델링 기법이 객체 상속과 유사하다.
- 상속관계 매핑은 객체의 상속 구조와 DB의 슈퍼타입 서브타입 관계를 매핑하는 것이다.
슈퍼타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 3가지 방법(DB 설계 방법)
1) 조인 전략
2) 단일 테이블 전략 : 논리 모델을 한 테이블로 합치는 것
3) 구현 클래스마다 테이블 전략 : 테이블을 각각 만들어서 알아서 공통되는 컬럼을 가지고 있는 것
* JPA 기본 전략은 단일 테이블 전략이다.
주요 어노테이션
- @Inheritance(strategy = InheritanceType.XXX)
- JOINED : 조인 전략
- SINGLE_TABLE : 단일 테이블 전략
- TABLE_PER_CLASS : 구현 클래스마다 테이블 전략
- @DiscriminatorColumn(name = "DTYPE")
- @DiscriminatorValue("XXX)
1) 조인 전략
아래는 예시 코드이다.
Item을 만들고, Album, Movie, Book 클래스를 만들어서 Item 클래스를 상속받는다.
@Entity
@Inheritance(strategy = InheritanceType.JOINED) // 조인 전략
public class Item {
@Id
@GeneratedValue
private Long id;
private String name;
private int price;
}
// 아래 Album, Movie, Book 클래스는 Item 클래스를 상속받음
@Entity
public class Album extends Item{
private String artist;
}
@Entity
public class Movie extends Item {
private String director;
private String actor;
}
@Entity
public class Book extends Item {
private String author;
private String isbn;
}
그리고 Main에서 실행할 코드를 살펴보자
public class RelationShipMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");// META-INF의 persistence.xml에 설정한 이름
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Movie movie = new Movie();
movie.setDirector("aaaa");
movie.setActor("bbbb");
movie.setName("짱구는 못말려");
movie.setPrice(10000);
em.persist(movie);
em.flush();
em.clear();
Movie findMovie = em.find(Movie.class, movie.getId());
System.out.println("findMovie = " + findMovie);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
movie 객체를 만들어서 setter로 데이터를 넣은 후 쿼리를 살펴보면, Item과 Movie 테이블에 insert를 하는 것을 확인할 수 있다.
그 후 코드를 살펴보자.
영속성 컨텍스트를 깨끗이 비우게되면 find를 할 때 join 쿼리를 날리는 것을 확인할 수 있다.
JPA가 상속관계에 따라서 필요한 insert와 join을 알아서 해준다!
여기서 Item Entity에 @DiscriminatorColumn 어노테이션을 추가를 하면 아래와 같이 DTYPE이라는 컬럼이 추가되어지는 것을 확인할 수 있다.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public class Item {
@Id
@GeneratedValue
private Long id;
private String name;
private int price;
}
테이블 조회를 해보면 Movie라는 값이 들어가 있는 것을 확인할 수 있다. 해당하는 엔티티 명을 넣어주는 것이다.
DTYPE이라는 컬럼명 말고 직접 설정해 주고 싶다면 name 속성을 이용하여 직접 설정할 수 있다.
@DiscriminatorValue 어노테이션을 사용하여 DTYPE 컬럼에 들어가는 데이터 값을 설정할 수 있다.
기본은 엔티티명이다.
@Entity
@DiscriminatorValue("A")
public class Album extends Item{
private String artist;
}
조인 전략의 장단점
조인 전략의 장점
- 테이블 정규화
- 외래 키 참조 무결성 제약조건 활용 가능
- 저장 공간 효율화
조인 전략의 단점
- 조회시 조인을 많이 사용, 성능 저하
- 조회 쿼리가 복잡함
- 데이터 저장시 INSERT SQL 2번 호출
2) 단일 테이블 전략
단일 테이블 전략은 InheritanceType을 SINGLE_TALBE로 변경해 주면 된다.
@DiscriminatorColumn 어노테이션이 없어도 DTYPE 컬럼을 추가해준다.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) // 단일 테이블 전략
// @DiscriminatorColumn // 해당 어노테이션이 없어도 DTYPE 컬럼을 추가해줌
public class Item {
@Id
@GeneratedValue
private Long id;
private String name;
private int price;
}
// 아래 Album, Movie, Book 클래스는 Item 클래스를 상속받음
@Entity
public class Album extends Item{
private String artist;
}
@Entity
public class Movie extends Item {
private String director;
private String actor;
}
@Entity
public class Book extends Item {
private String author;
private String isbn;
}
위와 같이 속성을 변경하고 실행해 보면 아래와 같이 단일 테이블로 쿼리를 날리는 것을 확인할 수 있다.
단일 테이블 전략의 장단점
단일 테이블 전략의 장점
- 조인이 필요 없으므로 일반적으로 조회 성능이 빠르며, 조회 쿼리가 단순하다.
단일 테이블 전략의 단점
- 자식 엔티티가 매핑한 컬럼은 모두 null 허용을 해야한다.
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 상황에 따라서 조회 성능이 오히려 느려질 수 있다.
3) 구현 클래스마다 테이블 전략
구현 클래스마다 테이블 전략은 ITEM 테이블에 있던 NAME, PRICE를 ALBUM, MOVIE, BOOK 테이블에 컬럼을 모두 추가하고, ITEM 테이블을 사용하지 않는 것이다. 즉, 각각의 테이블을 가지고 있는 형태라고 보면 된다.
아래 코드를 살펴보면, Item 클래스는 추상 클래스이다.
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) // 구현 클래스마다 테이블 전략
public abstract class Item {
@Id
@GeneratedValue
private Long id;
private String name;
private int price;
}
// 아래 Album, Movie, Book 클래스는 Item 클래스를 상속받음
@Entity
public class Album extends Item{
private String artist;
}
@Entity
public class Movie extends Item {
private String director;
private String actor;
}
@Entity
public class Book extends Item {
private String author;
private String isbn;
}
위 코드로 실행을 해보면 아래와 같이 테이블을 생성하는 쿼리를 확인할 수 있다.
그리고 나서 main 코드를 실행한 뒤 테이블을 확인해보면 Item 테이블은 생성되지 않으며, Movie 테이블에만 데이터가 저장되는 것을 확인할 수 있다.
구현 클래스마다 테이블 전략의 장단점
구현 클래스마다 테이블 전략의 장점
- 서브 타입을 명확하게 구분해서 처리할 때 효과적이다
- not null 제약조건 사용이 가능하다
구현 클래스마다 테이블 전략의 단점
- 여러 자식 테이블을 함께 조회할 때 성능이 느리다(UNION SQL 필요)
- 자식 테이블을 통합해서 쿼리하기가 어렵다
2. @MappedSuperclass
상속관계 매핑과는 관계가 없으며, 속성만 상속하는 것을 말한다.
즉, 공통 매핑 정보가 필요할 때 사용하는 것이다.
아래 그림으로 예시를 들자면
객체의 입장에서 id, name이 계속 나오고 있어서 반복적인 작업을 하고 있어 해당 속성만 상속받아서 사용하고 싶은 상황이다.
이걸 실제 코드를 작성해서 살펴보자
모든 테이블에 공통적으로 등록자, 등록일자, 수정자, 수정일자 라는 컬럼을 추가해야 되는 상황이 생긴다면 각 엔티티에 모두 해당 컬럼을 추가해주어야 하는데, 이 부분을 @MappedSuperclass를 사용하여 속성만 상속받아서 사용할 수 있다.
@Column 어노테이션을 사용하여 테이블의 컬럼명을 수정할 수 있다.
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@MappedSuperclass
public abstract class BaseEntity {
private String createdBy;
@Column(name = "regDt")
private LocalDateTime createdDate;
private String lastModifiedBy;
private LocalDateTime lastModifiedDate;
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public LocalDateTime getCreatedDate() {
return createdDate;
}
public void setCreatedDate(LocalDateTime createdDate) {
this.createdDate = createdDate;
}
public String getLastModifiedBy() {
return lastModifiedBy;
}
public void setLastModifiedBy(String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
public LocalDateTime getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(LocalDateTime lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
}
위에서 생성한 BaseEntity를 Member와 Team 클래스가 상속 받도록 해주고,
@Entity
public class Member extends BaseEntity {
// 생략
}
@Entity
public class Team extends BaseEntity {
// 생략
}
실행을 시키면 아래와 같이 쿼리가 나가는 것을 확인할 수 있다.
@MappedSuperclass 정리
- 해당 어노테이션은 상속관계 매핑이 아니고, 엔티티도 아니고 테이블과 매핑하는 것이 안된다.
- 속성만 내려주는 역할을 한다.
- 부모 클래스를 상속 받는 자식 클래스에 매핑 정보만 제공하며, 조회, 검색이 불가능 하다. ex) em.find(BaseEntity) 불가능
- 직접 생성해서 사용할 일이 없으므로 추상 클래스로 생성하는 것을 권장한다!
- 테이블과 관계가 없고, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할을 한다.
- 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용한다.
참고: JPA에서 @Entity 클래스는 엔티티나 @MappedSuperclass로 지정한 클래스만 상속이 가능하다.
'TIL > JPA' 카테고리의 다른 글
[TIL/JPA] 기본개념 : 영속성전이(CASCADE), 고아 객체 (0) | 2024.08.27 |
---|---|
[TIL/JPA] 기본개념 : 프록시와 연관관계 관리[즉시(EAGER)로딩, 지연(LAZY)로딩] (0) | 2024.08.27 |
[TIL/JPA] 기본 개념 : 다양한 연관관계 매핑 (0) | 2024.08.22 |
[TIL/JPA] 기본 개념 : 연관관계 매핑 기초 (0) | 2024.08.17 |
[TIL/JPA] 기본 개념 : 엔티티 매핑 (0) | 2024.08.12 |