JPA) 고급 연관관계 매핑 - 상속관계 매핑
Date: Updated:카테고리: java
상속관계 매핑 특징
- 관계형 데이터베이스는 상속관계X
- 슈퍼타입 서브타입 관계라는 모델링 기법이 객체 상속과 유사
- 상속관계 매핑 : 객체의 상속과 구조와 DB의 슈퍼타입 서브타입 관계를 매핑
- JPA는 DB의 슈퍼타입 서브타입 어떤 식으로 구현하든 전부 구현 가능하도록 지원함.
주요 어노테이션
@Inheritence(strategy=InheritanceType.XXX)
- 슈퍼타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 전략들을 사용한다.
- JOINED : 조인 전략
- SINGLE_TABLE : 단일 테이블 전략 (default)
- TABLE_PER_CLASS : 구현 클래스마다 테이블 전략
@DiscriminatorColumn(name="DTYPE")
- 슈퍼타입의 엔티티에 서브타입 구분을 위한 컬럼을 추가한다.
- DTYPE 이 default
@DiscriminatorValue("XXX")
- 서브타입 엔티티별로 DTYPE 컬럼에 들어갈 값을 지정할 수 있다.
- Entity 명이 default
구현 방법
- 조인 전략 (각각의 테이블로 변환)
- ITEM 이라는 테이블을 만들고 ALBUM, MOVIE, BOOK 테이블을 만들어서 나눈뒤 필요할 때 JOIN으로 가져온다.
- 예를 들어서 ALBUM 데이터를 추가할 때 ITEM에 대한 고유 ID는 ITEM 테이블에 두고 앨범의 아티스트 같은 고유 정보는 ALBUM 테이블에 둔다.
- ITEM_ID를 PK면서 FK로 두어 JOIN 전략을 구성할 때 활용한다.
- 보통 DTYPE 같이 구분 컬럼을 두어서 관리한다.
@Entity @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn(name = "ITEM_TYPE") public class Item { @Id @GenerateValuew private Long id; private String name; private int price; } @Entity @DiscriminatorValue public class Album extends Item { private String artist; } @Entity @DiscriminatorValue("M") public class Movie extends Item { private String director; private String actor; } @Entity @DiscriminatorValue public class Book extends Item { private String author; private String isbn; } ... Movie movie = new Movie(); movie.setDirector("aaaa"); movie.setActor("bbbb"); movie.setName("테스트영화"); movie.setPrice(10000); // ITEM 테이블과 MOVIE 테이블 2개에 insert가 되고 // MOVIE 테이블의 ID는 PK면서 ITEM 테이블의 FK 역할을 하게된다. em.persist(movie); em.flush(); em.clear(); // MOVIE 테이블과 ITEM 테이블을 ID로 inner join하여 가져옴 em.find(Movie.class, movie.getId());
- (부모 테이블 입장인) Item 엔티티에
@DiscriminatorColumn
를 추가하면 ITEM 테이블에 어떤 종류의 ITEM 인지 명시해주는DTYPE
컬럼이 추가된다.- default는
DTYPE
이나 name 속성으로 컬럼 명을 변경할 수 있다. (@DiscriminatorColumn(name="ITEM_TYPE")
) - 데이터베이스 입장에서는 row 만으로는 구분할 수 없기 때문에(어떤 종류의 ITEM 인지)
DTYPE
이 있는 것이 좋다.
- default는
- (자식 테이블 입장인) Movie 엔티티에
@DiscriminatorValue("M")
를 추가하면DTYPE
컬럼에 들어갈 값을 Movie가 아닌 M으로 지정할 수 있다.- default는 해당 엔티티 이름이다 (ex. Movie)
- 장점
- 테이블이 기본적으로 정규화가 되어있다.
- 깔끔하게 설계가 가능하다.
- 외래키 참조 무결성 제약조건을 활용 가능하다.
- 필요에 따라 외래키 참조를 통해 헤매지 않고 바로 데이터 조회를 할 수 있다.
- 예를 들어 주문 내역을 확인할 때 A 영화의 가격이 궁금하다면? (MOVIE join ITEM)
- 저장공간이 효율화 된다.
- 테이블들이 정규화가 되어있기 때문에 효율적이다.
- 테이블이 기본적으로 정규화가 되어있다.
- 단점
- 조회 시 조인을 많이 사용하기 때문에 성능이 저하된다.
- 조회 쿼리가 복잡해진다.
- 데이터 저장시 INSERT SQL을 2번 호출하게 된다.
- ITEM 테이블 한번, MOVIE 테이블 한번
- 단일 테이블 전략(통합 테이블로 변환)
- 논리 모델을 하나의 테이블로 합치는 전략
- 하나의 PK를 두고 (ITEM_ID) 각각의 컬럼으로 구분하여 관리한다.
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) public class Item { @Id @GenerateValuew private Long id; private String name; private int price; } @Entity @DiscriminatorValue public class Album extends Item { private String artist; } @Entity @DiscriminatorValue public class Movie extends Item { private String director; private String actor; } @Entity @DiscriminatorValue public class Book extends Item { private String author; private String isbn; }
- 단일 테이블 전략 사용 시 부모 역할을 하는 ITEM 테이블에 ALBUM, MOVIE, BOOK 테이블의 컬럼이 일렬로 전부 추가 된다.
- ALBUM, MOVIE, BOOK 테이블은 따로 생성되지 않는다.
- 테이블 하나에 모든 컬럼이 포함되있기 때문에 성능상 매우 유리하다. (쿼리 복잡도가 낮으므로)
- 단일 테이블 전략은
@DiscriminatorColumn
이 없어도DTYPE
컬럼이 자동 생성된다.- 조인 전략은 테이블이 나뉘어져 있기 때문에
DTYPE
컬럼이 없어도 다른 방법을 통해 알아낼 수 있다.- FK를 기준으로 조인쿼리를 날린 다던지 등
- 그러나 단일 테이블 전략은 하나의 테이블에 전부 들어가 있기 때문에 구분할 수 있는
DTYPE
컬럼이 필수로 있어야 한다.
- 조인 전략은 테이블이 나뉘어져 있기 때문에
- 장점
- 조인이 필요 없으므로 일반적으로 조회 성능이 빠르다.
- 테이블 하나만 대상으로 쿼리를 날리면 되므로 복잡도가 낮다 = 성능이 좋다
- 조회 쿼리가 단순하다.
- 조인이 필요 없으므로 일반적으로 조회 성능이 빠르다.
- 단점
- 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.
- MOVIE에 관한 내용만 넣고 싶다면? (artist, author, isbn 컬럼 셋다 null이 됨)
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다.
- 상황에 따라 조회 성능이 오히려 느려질 수 있다. (이정도가 되려면 볼륨이 임계점을 넘어야 하며 그럴일은 거의 없다.)
- 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.
- 구현 클래스마다 테이블 전략(서브타입 테이블로 변환)
- 조인 전략에 사용한 것 처럼 공통이 되는 ITEM 테이블을 두지 않고 자식 테이블이 각각 필요한 컬럼들을 전부 가진다.
@Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class Item { @Id @GenerateValuew private Long id; private String name; private int price; } @Entity @DiscriminatorValue public class Album extends Item { private String artist; } @Entity @DiscriminatorValue public class Movie extends Item { private String director; private String actor; } @Entity @DiscriminatorValue public class Book extends Item { private String author; private String isbn; } Movie movie = new Movie(); movie.setDirector("aaaa"); movie.setActor("bbbb"); movie.setName("테스트영화"); movie.setPrice(10000); em.persist(movie); em.flush(); em.clear(); // ALBUM, MOVIE, BOOK 테이블을 union all로 조회 em.find(Item.class, movie.getId());
- 직접 사용하는게 아니기 때문에 부모 테이블의 역할인 ITEM 엔티티를 추상 클래스로 작성해야 한다.
- ALBUM, MOVIE, BOOK 테이블에 각각 ID 컬럼이 생성된다.
- 단순하게 값을 넣고 뺄때는 좋지만 부모 타입의 엔티티를 조회할 때 문제가 생긴다.
- ALBUM, MOVIE, BOOK 테이블에 전부 ITEM 테이블에게서 받은 ID 컬럼이 존재하므로 JPA가 Union all로 조회하기 때문이다.
- 이 전략은 데이터베이스 설계자와 ORM 전문가 둘다 추천하지 않는 전략이다.
- 테이블 별로 컬럼이 각각 생성되어 서로 연관지을 수 있는 관계가 사라진다.
- 새로 컬럼이 추가될 때 마다 전부 추가 해줘야 한다.
- 장점
- 서브 타입을 명확하게 구분해서 처리할 때 효과적이다.
- 테이블들이 전부 각각 떨어져 있으므로 not null 제약조건 사용이 가능하다.
- 단점
- 여러 자식 테이블을 함께 조회할 때 성능이 느리다. (union)
- 자식 테이블을 통합해서 쿼리하기 어렵다.
- 조인 전략에 사용한 것 처럼 공통이 되는 ITEM 테이블을 두지 않고 자식 테이블이 각각 필요한 컬럼들을 전부 가진다.
📣 Reference
본 포스팅은 김영한님의 강의를 듣고 스스로 정리 및 추가한 내용입니다.
댓글남기기