- 객체에는 상속관계가 있지만 관계형 데이터베이스는 상속 관계가 없다
- 관계형 데이터베이스에는
슈퍼타입 서브타입 관계
라는 모델링 기법이 그나마 객체 상속과 유사하다.상속관계 매핑
: 객체의 상속과 구조와 DB의 슈퍼타입 서브타입 관계를 매핑하는 것이다.
슈퍼타입 서브타입 논리 모델
을 실제 물리 모델
로 구현하는 3가지 방법
- 각각 테이블로 변환 👉
조인 전략
- Item 테이블을 Album, Movie, Book 테이블과 조인한다
- 통합 테이블로 변환 👉
단일 테이블 전략
- 논리 모델을 하나의 테이블로 합친다.
- 서브타입 테이블로 변환 👉
구현 클래스마다 테이블 전략
데이터베이스 논리모델을 물리 모델로 구현하는 방법이 3가지지만, 객체 입장에서는 모두 동일하다.
조인 전략
과 단일 테이블 전략
중에서만 고민하면 된다. (구현 클래스마다 테이블 전략
은 사용하지 말것)
- 비지니스적으로 중요하고 복잡한 경우 👉
조인 전략
- 비지니스적으로 단순한 경우 👉
단일 테이블 전략
@Inheritance(strategy=InheritanceType.XXX)
@Inheritance(strategy=InheritanceType.JOINED)
: 조인 전략@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
: 단일 테이블 전략@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
: 구현 클래스마다 테이블 전략@DiscriminatorColumn(name=“DTYPE”)
- 운영상 DTYPE은 항상 있는게 좋다
@DiscriminatorValue(“XXX”)
@Inheritance(strategy = InheritanceType.JOINED)
- 장점
- 테이블 정규화
- 외래 키 참조 무결성 제약조건을 활용할 수 있다
- 예시) 주문 데이터 정보를 얻기 위해
ITEM_ID
조회가 필요할 경우 Item 테이블만 조회하면 된다. (Album, Movie, Book 테이블을 조회할 필요 없다)- 저장공간 효율화
- 단점 (딱히 큰 단점은 아니다)
- 조회 시 조인을 많이 사용하기 때문에 성능 저하
- 조회 쿼리가 복잡함
- 데이터 저장시 INSERT SQL 2번 호출
단일 테이블 전략
에 비해 복잡하다
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn // DTYPE을 생성해준다
public abstract class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
@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;
}
@DiscriminatorColumn
- DTYPE 이라는 속성에 자식 클래스의 엔티티 이름이 들어간다.
@DiscriminatorColumn(name = "DIS_TYPE")
처럼 속성이름을 바꿀 수 있다.- 자식 클래스에
@DiscriminatorValue("A")
어노테이션을 달면 DTYPE에 엔티티 이름 대신A
가 들어간다
Movie movie = new Movie();
movie.setDirector("aaa");
movie.setActor("bbb");
movie.setName("바람과함께사라지다");
movie.setPrice(10000);
em.persist(movie);
em.flush(); // 영속성 컨택스트에 있는 쿼리를 DB에 날리고
em.clear(); // 1차캐시를 깔끔하게 지운다.
Movie findMovie = em.find(Movie.class, movie.getId());
System.out.println("findMovie = " + findMovie.getName());
System.out.println("=== commit ===");
tx.commit();
Album 정보를 저장하면 👉 Item 테이블에
이름
,가격
정보를 insert하고 Album 테이블에아티스트
정보를 insert한다.
Ablum 정보 조회시 👉 Item 테이블과 Album 테이블을 조인하여 반환한다.
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
DTYPE으로 Album, Movie, Book을 구분한다 👉 @DiscriminatorColumn
어노테이션을 안 달아줘도 DTYPE이 생긴다
- 장점
- 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
- 조회 쿼리가 단순하다 (조인할 필요가 없기 때문)
- 단점
- 자식 엔티티가 매핑한 컬럼은 모두 null 허용해줘야 한다. (치명적인 단점)
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다.
- 상황에 따라서
조인 전략
보다 조회 성능이 오히려 느려질 수 있다. (일반적으로는조인 전략
보다 성능이 빠르다)
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
// @DiscriminatorColumn 어노테이션 없어도 DTYPE 자동 생성
public abstract class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
@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;
}
Movie movie = new Movie();
movie.setDirector("aaa");
movie.setActor("bbb");
movie.setName("바람과함께사라지다");
movie.setPrice(10000);
em.persist(movie);
em.flush(); // 영속성 컨택스트에 있는 쿼리를 DB에 날리고
em.clear(); // 1차캐시를 깔끔하게 지운다.
Movie findMovie = em.find(Movie.class, movie.getId());
System.out.println("findMovie = " + findMovie.getName());
System.out.println("=== commit ===");
tx.commit();
insert 쿼리도 한번만 나가고 select 쿼리도 매우 간단하다.
결론부터 말하자면, 사용하면 안되는 전략이다.
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
📌이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천하지 않는다
- 장점
- 서브 타입을 명확하게 구분해서 처리할 때 효과적
- not null 제약조건 사용 가능
- 단점
- 여러 자식 테이블을 함께 조회할 때 성능이 느리다 (UNION SQL 필요)
- 자식 테이블(Album, Movie, Book)을 통합해서 쿼리하기 어려움
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@DiscriminatorColumn // DTYPE을 생성해준다
public abstract class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
@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;
}
ITEM 테이블을 없애고 Album, Movie, Book 테이블에 중복 속성 (
Name, Price
)를 넣는다.
👉Album, Movie, Book
테이블만 만들어지고Item
테이블은 없다.
Movie movie = new Movie();
movie.setDirector("aaa");
movie.setActor("bbb");
movie.setName("바람과함께사라지다");
movie.setPrice(10000);
em.persist(movie);
em.flush(); // 영속성 컨택스트에 있는 쿼리를 DB에 날리고
em.clear(); // 1차캐시를 깔끔하게 지운다.
Movie findMovie = em.find(Movie.class, movie.getId());
System.out.println("findMovie = " + findMovie.getName());
System.out.println("=== commit ===");
tx.commit();
단순하게 데이터를 넣고 뺴는 경우만 고려하면 괜찮은 전략처럼 보인다.
BUT 👇
Movie movie = new Movie();
movie.setDirector("aaa");
movie.setActor("bbb");
movie.setName("바람과함께사라지다");
movie.setPrice(10000);
em.persist(movie);
em.flush(); // 영속성 컨택스트에 있는 쿼리를 DB에 날리고
em.clear(); // 1차캐시를 깔끔하게 지운다.
Item item = em.find(Item.class, movie.getId());
System.out.println("item = " + item.getName());
System.out.println("=== commit ===");
tx.commit();
Item item = em.find(Item.class, movie.getId());
- 만약 Item 타입(부모 클래스)으로 Movie를 조회하게 되면
JPA는union all
연산을 사용하여 Album, Movie, Book 테이블 모두 살펴본다
👉 성능상 안 좋다
객체입장에서 공통 매핑 정보가 필요할 때 사용한다 (id, name)
- 객체 입장에서 속성만 상속하고 싶을 경우
@MappedSuperclass
를 사용한다.- 데이터베이스는 완전 다른 테이블이다.
@MappedSuperclass // 매핑 정보만 받는 슈퍼 클래스
public abstract class BaseEntity {
private String createdBy;
private LocalDateTime createdDate;
private String lastModifiedBy;
private LocalDateTime lastModifiedDate;
}
@Entity
public class Member extends BaseEntity{
…
}
@Entity
public class Team extends BaseEntity{
…
}
// 참고
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Item extends BaseEntity {
…
}
---
Member member = new Member();
member.setUserName("user1");
member.setCreatedBy("kim");
member.setCreatedDate(LocalDateTime.now());
em.persist(member);
em.flush(); // 영속성 컨택스트에 있는 쿼리를 DB에 날리고
em.clear(); // 1차캐시를 깔끔하게 지운다.
System.out.println("=== commit ===");
tx.commit();
- 상속관계 매핑과 관계가 없다.
- 엔티티도 아니고 테이블과 매핑도 되지 않는다.
- 부모 클래스를 상속 받는 자식 클래스에 매핑 정보만 제공
- 조회, 검색 불가(
em.find(BaseEntity)
불가)
BaseEntity baseEntity = em.find(BaseEntity.class, member.getId());
이게 불가능하다는 의미- 직접 생성해서 사용할 일이 없으므로 추상 클래스(abstract) 권장
- 테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑정보를 모으는 역할
- 주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용
- 참고)
@Entity
클래스는@Entity
로 지정한 클래스나@MappedSuperclass
로 지정한 클래스만 상속 가능하다