-
[JPA] 고급매핑 - 상속관계 매핑DEV/JPA 2024. 4. 5. 00:07
* 정보전달의 목적이 아닌 개인 스터디 정리 글 입니다.
강의 : 인프런 <자바 ORM 표준 JPA 프로그래밍 기본편>
교육자 : 김영한
상속관계 매핑
: 객체의 상속 구조와 DB의 슈퍼타입 서브타입 관계를 매핑
- 관계형 데이터베이스는 상속관계가 없다.
- 슈퍼타입 서브타입 관계의 모델링 기법이 객체 상속과 유사하다.
* DB 논리모델을 물리적으로 구현할수있는 방법
- 조인전략 : 모든 클래스를 각각의 테이블로 변환
- 단일 테이블 전략 : 통합된 테이블 한개로 변환
- 구현 클래스마다 테이블 전략 : 서브타입 테이블로 변환 (슈퍼타입 클래스 제외)
* 주요 어노테이션
- @Inheritance(strategy=InheritanceType.XXX)
- JOINED : 조인 전략
- SINGLE_TABLE : 단일 테이블 전략
- TABLE_PER_CLASS : 구현 클래스마다 테이블 전략
- @DiscriminatorColumn : 하위 엔티티명이 DTYPE 필드로 들어옴 (name 속성 바꿀수있음)
- @DiscriminatorValue("XXX") : DTYPE을 엔티티명이 아닌 다른 값으로 바꿀수있음
조건 ) 슈퍼타입 ITEM 클래스와 서브타입 ALBUM, MOVIE, BOOK 클래스가 존재 할때
* 조인전략
- 상위테이블과 하위테이블로 나누어 저장하는 방법
- 상위 엔티티를 상속받음
- 상위, 하위 테이블 각각 insert 됨
- 데이터를 가져올땐 상위,하위테이블을 조인해서 가져옴장점
- 테이블 정규화
- 외래키 참조 무결성 제약조건 활용가능
- 저장공간 효율화
단점
- 조회시 조인을 많이사용 -> 성능 저하
- 조회 쿼리가 복잡
- 데이터 저장시 INSERT SQL 2번 호출
상위 추상클래스 Item
import jakarta.persistence.*; @Entity @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn public abstract class Item { @Id @GeneratedValue private long id; private String name; private int price; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } }
하위 서브클래스 Movie, Album, Bookimport jakarta.persistence.Entity; @Entity public class Movie extends Item { private String director; private String actor; public String getDirector() { return director; } public void setDirector(String director) { this.director = director; } public String getActor() { return actor; } public void setActor(String actor) { this.actor = actor; } } @Entity public class Album extends Item { private String artist; } @Entity public class Book extends Item { private String author; private String isbn; }
상속관계를 JOIN으로 설정 후 Movie 객체에 값을 저장 및 출력
import jakarta.persistence.*; import java.util.List; public class JpaMain { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); try { Movie movie = new Movie(); movie.setDirector("장재현"); movie.setName("파묘"); movie.setActor("최민식"); movie.setPrice(12000); 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(); } }
결과1 ) 테이블이 모두 각각 생성됨
결과2 ) Item, Movie 각각 insert 쿼리가 두번 나감
결과3 ) 값을 가져올때 Join해서 가져옴
결과 4 ) DB가 각각 생성된 모습
* 단일테이블전략- 모든 클래스를 하나의 클래스에 통합하여 저장하는 방법
- @DiscriminatorColumn 사용 안해도 DTYPE이 자동으로 생성됨장점
- 조인이 필요 없어 일반적으로 조회 성능이 빠름
- 조회 쿼리가 단순
단점
- 자식 엔티티가 매핑한 컬럼은 모두 NULL이 허용됨
- 단일 테이블에 모든것을 저장하므로 테이블이 커질 수 있다.
상황에 따라 조회 성능이 오히려 느려질 수 있다. -> 임계점이 넘어야하는데 극히 드물긴함
나머지는 그대로 두고,
Item 클래스에서 상속관계 매핑 타입을 SIGLE_TABLE로 변경, @DiscriminatorColumn 제거해도 상관X
import jakarta.persistence.*; @Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) public abstract class Item { @Id @GeneratedValue private long id; private String name; private int price; ... }
결과1 ) 테이블 하나만 생성되고 모든 필드가 들어감
결과2 ) 한번만 insert됨
결과3 ) select도 조인없이 한번에 가능
* 구현 클래스마다 테이블 전략
- 슈퍼타입 클래스를 제외한 서브타입 클래스가 각각 테이블로 저장
- @DiscriminatorColumn 어노테이션 필요없음 (각각 테이블로 관리하기 때문)
- 이 전략은 데이터베이스 설계자와 ORM 전문가 둘다 추천하지 않는 방식장점
- 서브 타입을 명확하게 구분해서 처리할 때 효과적
- NOT NULL 제약조건 사용 가능
단점
- 여러 자식 테이블을 함께 조회할 때 성능이 느림 (UNION SQL 필요)
- 자식 테이블을 통합해서 쿼리하기 어려움
마찬가지로 상속관계 매핑 타입만 TABLE_PER_CLASS로 변경
import jakarta.persistence.*; @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class Item { @Id @GeneratedValue private long id; private String name; private int price; ... }
id로 Movie객체가 아닌 Item객체 가져오기
import jakarta.persistence.*; public class JpaMain { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); try { Movie movie = new Movie(); movie.setDirector("장재현"); movie.setName("파묘"); movie.setActor("최민식"); movie.setPrice(12000); em.persist(movie); em.flush(); em.clear(); Item findItem = em.find(Item.class, movie.getId()); System.out.println("findItem ==> " + findItem); tx.commit(); } catch (Exception e) { tx.rollback(); } finally { em.close(); } emf.close(); } }
결과1 ) Item 테이블을 제외한 Movie, Book, Album 테이블만 생성
결과2 ) 1개의 테이블에만 insert
결과3 ) 상위 타입의 값을 가져오려고 테이블을 union하는 모습 (생략됨)
@MappedSuperclass
: 공통 매핑 정보가 필요할 때 사용 (id, name, date ...)- 상속관계 매핑이 아님
- 엔티티가 아님, 따라서 테이블과 매핑하는것이 아님
- 부모클래스를 상속받는 자식클래스에 매핑 정보만 제공
- 조회, 검색 불가 (em.find(BaseEntity) 불가)
- 직접 생성해서 사용할 일이 없으므로 추상 클래스로 만들것을 권장
- 테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할
참고 : @Entity클래스는 엔티티나 @MappedSuperclass로 지정한 클래스만 상속 가능
공통적으로 사용할 생성자, 생성일, 수정자, 수정일 정보를 모은 BaseEntity
import jakarta.persistence.MappedSuperclass; import java.time.LocalDateTime; @MappedSuperclass public abstract class BaseEntity { private String createdBy; 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; } }
Member, Team 클래스 상속받기
@Entity public class Member extends BaseEntity { @Id @GeneratedValue @Column(name = "MEMBER_ID") private Long id; @Column(name = "USERNAME") private String name; ... } @Entity public class Team extends BaseEntity { @Id @GeneratedValue @Column(name = "TEAM_ID") private Long id; private String name; ... }
결과 ) Member, Team 테이블에 필드가 각각 들어가는 모습 확인
'DEV > JPA' 카테고리의 다른 글
[JPA] 값 타입 (엔티티 타입과 비교 / 기본값타입 / 임베디드타입 / 컬렉션 값타입) (0) 2024.06.01 [JPA] 프록시와 연관관계 관리 (즉시로딩/지연로딩/영속성전이/고아객체) (0) 2024.04.29 [JPA] 다양한 연관 관계 매핑 ( N:1 / 1:N / 1:1 / N:M ) (1) 2024.04.04 [JPA] 연관관계 매핑 기초 (0) 2024.03.29 [JPA] 엔티티 매핑 (0) 2024.03.07 - @Inheritance(strategy=InheritanceType.XXX)