티스토리 뷰
소스 코드는 여기 있습니다. (commit hash: c93b796)
> git clone https://github.com/lcalmsky/jpa > git checkout c93b796
Warning: 이번 소스 코드는 최종 커밋 기준으로 작성되어 있어 모든 테스트 결과를 정확히 확인할 순 없으니 참고 부탁드립니다.
Overview
객체의 상속관계를 테이블로 어떻게 표현할 수 있는지 알아봅니다.
슈퍼타입, 서브타입 관계
관계형 데이터베이스에는 상속 관계가 존재하지 않습니다.
반면 객체는 상속관계를 나타낼 수 있는데요, 이 상속과 유사한 방법이 DB 모델링 기법에도 존재합니다.
바로 슈퍼타입, 서브타입 관계인데 확장된 ER 모델이라고 부릅니다.
슈퍼-서브 타입이 도출되는 과정은 다음과 같습니다.
- 공통된 데이터만 슈퍼 타입에 남김
- 개별 속성은 서브타입으로 구성
- 서브타입간에 교집합이 존재하지 않을 경우 배타적(exclusive) 서브 카테고리라고 함
- 서브타입간에 교집합이 존재할 경우 포괄적(inclusive) 서브 카테고리라고 함
객체를 추상화시키는 과정과 매우 유사하죠?
각 관계를 표현하기 위한 방법은 다음과 같습니다.
- Rollup
- 하나의 테이블로 통합
- 슈퍼타입에 서브타입의 모든 컬럼을 통합하여 하나의 테이블로 생성
- Rolldown
- 여러 개의 테이블로 분리
- 슈퍼타입을 각각의 서브타입에 추가하여 서브타입별로 테이블 생성
- Identity
- 각각의 테이블로 분리
- 슈퍼타입과 서브타입 각각을 테이블로 생성
이 관계를 JPA를 이용해 객체에 적용하여 DB와 매핑할 수 있습니다.
상속관계 매핑
객체의 상속 구조와 DB의 슈퍼타입, 서브타입 관계를 매핑합니다.
구현하는 방법은 다음과 같습니다.
- Rollup: 단일 Entity 매핑
- Rolldown: 구현 클래스마다 Entity 매핑
- Identity: Join 사용
애너테이션
상속관계 매핑을 위해 사용하는 애너테이션은 다음과 같습니다.
- @Inheritance
- 상속 관계를 나타냄
- strategy attribute 사용
- InheritanceType.JOINED
- InheritanceType.SINGLE_TABLE
- InheritanceType.TABLE_PER_CLASS
- @DiscriminatorColumn(name="DTYPE")
- 타입을 구분하는데 특정 컬럼을 사용
- @DiscriminatorValue("XXX")
- 타입을 구분하는데 특정 값을 사용
@Inheritance(stragegy=InheritanceType.JOINED)
JOINED
전략을 사용하면 테이블 정규화에 유리하고, FK
참조시 무결성을 보장할 수 있으며, 저장 공간을 효율적으로 사용할 수 있습니다.
하지만 조회를 위해 쿼리에 join
을 많이 사용하기 때문에 성능이 저하되고 쿼리가 복잡해지며 insert
쿼리를 수행할 땐 두 번씩 호출됩니다.
@Inheritance(stragegy=InheritanceType.SINGLE_TABLE)
SINGLE_TABLE
전략은 쿼리가 단순하고 join
을 사용하지 않아도 되기 때문에 성능이 좋습니다.
반면에 자식 Entity
가 매핑한 컬럼은 모두 null
을 허용해야하고 모든 내용을 다 저장하기 때문에 테이블이 커질 수 있어 상황에 따라 조회 속도가 오히려 느려질 수도 있습니다.
@Inheritance(stragegy=InheritanceType.TABLE_PER_CLASS)
TABLE_PER_CLASS
전략을 사용하면 서브 타입을 명확하게 구분하여 처리할 때 효과적이고 컬럼에 not null
을 명시하여 데이터를 제한할 수 있습니다.
반면에 여러 테이블을 같이 조회해야 할 때 union
을 사용해야 해서 조회 성능이 매우 느려집니다.
이 방법은 DB
에서도 JPA
에서도 추천하지 않는 방법입니다.
구현 및 테스트
위 관계들을 JPA
에서 어떻게 매핑하는지 예시와 함께 확인해보겠습니다.
상속만 구현
package com.tistory.jaimenote.jpa.inheritance.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
private Integer price;
}
package com.tistory.jaimenote.jpa.inheritance.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
public class Song extends Product {
@Id
@GeneratedValue
private Long id;
private String singer;
private String writer;
}
package com.tistory.jaimenote.jpa.inheritance.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
public class Movie extends Product {
@Id
@GeneratedValue
private Long id;
private String director;
private String actor;
}
package com.tistory.jaimenote.jpa.inheritance.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
public class Book extends Product {
@Id
@GeneratedValue
private Long id;
private String author;
private String isbn;
}
객체간 상속관계만 지정한 상태에서 애플리케이션을 실행해보겠습니다.
2022-07-01 23:31:40.537 DEBUG 24303 --- [ main] org.hibernate.SQL :
create table product (
dtype varchar(31) not null,
id bigint not null,
name varchar(255),
price integer,
author varchar(255),
isbn varchar(255),
actor varchar(255),
director varchar(255),
singer varchar(255),
writer varchar(255),
primary key (id)
)
로그를 보면 SINGLE_TABLE
전략으로 실행된 것을 확인할 수 있습니다.
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
이렇게 명시해주지 않아도 기본 값으로 동작하는 것을 확인할 수 있습니다.
JOINED 전략
JOINED
전략을 사용하려면 직접 부모 객체에 명시해줘야 하고 부모 객체의 필드를 자식이 공유해야 하기 떄문에 관련 부분을 수정하였습니다.
package com.tistory.jaimenote.jpa.inheritance.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
@Inheritance(strategy = InheritanceType.JOINED)
public class Product {
@Id
@GeneratedValue
private Long id;
@Setter(AccessLevel.PROTECTED)
private String name;
@Setter(AccessLevel.PROTECTED)
private Integer price;
}
package com.tistory.jaimenote.jpa.inheritance.entity;
import javax.persistence.Entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString(callSuper = true)
public class Song extends Product {
private String singer;
private String writer;
private Song(String singer, String writer, String name, Integer price) {
this.singer = singer;
this.writer = writer;
setName(name);
setPrice(price);
}
public static Song create(String singer, String writer, String name, Integer price) {
return new Song(singer, writer, name, price);
}
}
Song
클래스만 테스트를 위해 static
생성자를 추가하였습니다. 나머지 클래스도 마찬가지로 부모의 name
, price
값을 설정해주어야 하지만 생략하겠습니다.
package com.tistory.jaimenote.jpa.inheritance.entity;
import javax.persistence.Entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
public class Movie extends Product {
private String director;
private String actor;
}
package com.tistory.jaimenote.jpa.inheritance.entity;
import javax.persistence.Entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
public class Book extends Product {
private String author;
private String isbn;
}
다시 실행해보면,
2022-07-01 23:38:41.715 DEBUG 24860 --- [ main] org.hibernate.SQL :
create table product (
id bigint not null,
name varchar(255),
price integer,
primary key (id)
)
2022-07-01 23:38:41.716 DEBUG 24860 --- [ main] org.hibernate.SQL :
create table song (
singer varchar(255),
writer varchar(255),
id bigint not null,
primary key (id)
)
2022-07-01 23:38:41.700 DEBUG 24860 --- [ main] org.hibernate.SQL :
create table book (
author varchar(255),
isbn varchar(255),
id bigint not null,
primary key (id)
)
2022-07-01 23:38:41.710 DEBUG 24860 --- [ main] org.hibernate.SQL :
create table movie (
actor varchar(255),
director varchar(255),
id bigint not null,
primary key (id)
)
2022-07-01 23:38:41.750 DEBUG 24860 --- [ main] org.hibernate.SQL :
alter table song
add constraint FK9fdrc2217ma14hcfbjjuu7552
foreign key (id)
references product
2022-07-01 23:38:41.717 DEBUG 24860 --- [ main] org.hibernate.SQL :
alter table book
add constraint FK8cjf4cjanicu58p2l5t8d9xvu
foreign key (id)
references product
2022-07-01 23:38:41.741 DEBUG 24860 --- [ main] org.hibernate.SQL :
alter table movie
add constraint FKann45li0a78wurkcpk3x3mpb7
foreign key (id)
references product
테이블이 각각 생성되고 FK
로 ID를 매핑하는 것을 확인할 수 있습니다.
그리고 어떻게 동작하는지 확인하기 위해 테스트 클래스를 작성해 insert
, select
쿼리를 확인해보겠습니다.
package com.tistory.jaimenote.jpa.inheritance.infra.repository;
import com.tistory.jaimenote.jpa.inheritance.entity.Song;
import java.util.List;
import javax.persistence.PersistenceContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
@DataJpaTest
class SongRepositoryTest {
@Autowired
SongRepository songRepository;
@BeforeEach
void setup() {
Song song = Song.create("IU", "IU", "너의 의미", 500);
songRepository.save(song);
}
@Test
void findSongTest() {
List<Song> songs = songRepository.findAll();
for (Song song : songs) {
System.out.println(song);
}
}
}
테스트를 실행해보면,
Hibernate:
insert
into
product
(name, price, id)
values
(?, ?, ?)
2022-07-02 02:07:24.315 TRACE 29540 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [너의 의미]
2022-07-02 02:07:24.317 TRACE 29540 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [500]
2022-07-02 02:07:24.318 TRACE 29540 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [1]
Hibernate:
insert
into
song
(singer, writer, id)
values
(?, ?, ?)
2022-07-02 02:07:24.321 TRACE 29540 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [IU]
2022-07-02 02:07:24.321 TRACE 29540 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [IU]
2022-07-02 02:07:24.322 TRACE 29540 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [1]
Hibernate:
select
song0_.id as id1_9_,
song0_1_.name as name2_9_,
song0_1_.price as price3_9_,
song0_.singer as singer1_10_,
song0_.writer as writer2_10_
from
song song0_
inner join
product song0_1_
on song0_.id=song0_1_.id
Song(super=Product(id=1, name=너의 의미, price=500), singer=IU, writer=IU)
insert
가 2회 발생하고 select
시에는 inner join
으로 product
와 song
테이블의 데이터를 조회해오는 것을 확인할 수 있습니다.
JOINED
전략을 사용하면서 @DiscriminatorColumn
을 사용했을 때는 어떻게 바뀌는지 확인해보겠습니다.
부모 클래스에 @DiscriminatorColumn
애너테이션을 추가합니다.
// 생략
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public class Product {
// 생략
}
name
attribute를 이용해 컬럼명을 수정할 수 있습니다. 아무것도 지정하지 않으면 DTYPE
을 기본 값으로 사용합니다.
자식 클래스에는 @DiscriminatorValue
애너테이션을 추가합니다.
// 생략
@DiscriminatorValue("song")
public class Song extends Product {
// 생략
}
// 생략
@DiscriminatorValue("movie")
public class Movie extends Product {
// 생략
}
// 생략
@DiscriminatorValue("Book")
public class Book extends Product {
// 생략
}
다시 실행해보면,
Hibernate:
create table product (
dtype varchar(31) not null,
id bigint not null,
name varchar(255),
price integer,
primary key (id)
)
dtype
이라는 컬럼이 자동으로 생성된 것을 확인할 수 있고, insert
, select
쿼리를 확인해보면,
Hibernate:
insert
into
product
(name, price, dtype, id)
values
(?, ?, 'song', ?)
2022-07-02 02:16:34.323 TRACE 30335 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [너의 의미]
2022-07-02 02:16:34.325 TRACE 30335 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [500]
2022-07-02 02:16:34.326 TRACE 30335 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [1]
Hibernate:
insert
into
song
(singer, writer, id)
values
(?, ?, ?)
2022-07-02 02:16:34.330 TRACE 30335 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [IU]
2022-07-02 02:16:34.330 TRACE 30335 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [IU]
2022-07-02 02:16:34.330 TRACE 30335 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [1]
Hibernate:
select
song0_.id as id2_9_,
song0_1_.name as name3_9_,
song0_1_.price as price4_9_,
song0_.singer as singer1_10_,
song0_.writer as writer2_10_
from
song song0_
inner join
product song0_1_
on song0_.id=song0_1_.id
Song(super=Product(id=1, name=너의 의미, price=500), singer=IU, writer=IU)
아쉽게도 song 컬럼이 추가된 것 외에는 이전 쿼리와 동일합니다.
그렇다면 SINGLE_TABLE
전략을 사용하면 어떻게 될까요?
SINGLE_TABLE 전략
// 생략
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn
public class Product {
// 생략
}
부모클래스에서 상속 전략을 수정하였습니다.
그리고 테스트를 다시 실행해보면,
Hibernate:
create table product (
dtype varchar(31) not null,
id bigint not null,
name varchar(255),
price integer,
author varchar(255),
isbn varchar(255),
actor varchar(255),
director varchar(255),
singer varchar(255),
writer varchar(255),
primary key (id)
)
product
테이블 한 개만 생성되었고,
Hibernate:
insert
into
product
(name, price, singer, writer, dtype, id)
values
(?, ?, ?, ?, 'song', ?)
2022-07-02 02:19:03.145 TRACE 30535 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [너의 의미]
2022-07-02 02:19:03.147 TRACE 30535 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [500]
2022-07-02 02:19:03.147 TRACE 30535 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [IU]
2022-07-02 02:19:03.147 TRACE 30535 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [VARCHAR] - [IU]
2022-07-02 02:19:03.148 TRACE 30535 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [5] as [BIGINT] - [1]
Hibernate:
select
song0_.id as id2_7_,
song0_.name as name3_7_,
song0_.price as price4_7_,
song0_.singer as singer9_7_,
song0_.writer as writer10_7_
from
product song0_
where
song0_.dtype='song'
Song(super=Product(id=1, name=너의 의미, price=500), singer=IU, writer=IU)
insert
, select
쿼리가 1회만 발생하였고 join
대신 where
절에 dtype
을 사용한 것을 확인할 수 있습니다.
사실 SINGLE_TABLE
전략을 사용하면 @DiscriminatorColumn
을 사용하지 않아도 자동으로 DTYPE
컬럼을 생성합니다.
하나의 테이블 안에서 타입을 구분해가면서 저장하려면 추가 컬럼을 사용하지 않고는 불가능하기 때문입니다.
자식 클래스에서도 마찬가지로 @DiscriminatorValue
를 모두 제거하여도 테스트에 성공하고 이 때 값은 Entity
이름과 동일하게 기본 값이 설정됩니다.
TABLE_PER_CLASS 전략
마지막으로 TABLE_PER_CLASS
로 설정하여 테이블이 어떻게 생성되는지 확인해보겠습니다.
// 생략
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Product {
// 생략
}
Hibernate:
create table product (
id bigint not null,
name varchar(255),
price integer,
primary key (id)
)
Hibernate:
create table song (
id bigint not null,
name varchar(255),
price integer,
singer varchar(255),
writer varchar(255),
primary key (id)
)
Hibernate:
create table movie (
id bigint not null,
name varchar(255),
price integer,
actor varchar(255),
director varchar(255),
primary key (id)
)
Hibernate:
create table book (
id bigint not null,
name varchar(255),
price integer,
author varchar(255),
isbn varchar(255),
primary key (id)
)
테이블이 생성될 때 product
의 속성인 name
, price
가 모든 서브 타입에 추가된 것을 확인할 수 있습니다.
테스트를 실행해보면,
Hibernate:
insert
into
song
(name, price, singer, writer, id)
values
(?, ?, ?, ?, ?)
2022-07-02 02:26:15.882 TRACE 31121 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [너의 의미]
2022-07-02 02:26:15.885 TRACE 31121 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [500]
2022-07-02 02:26:15.886 TRACE 31121 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [IU]
2022-07-02 02:26:15.886 TRACE 31121 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [VARCHAR] - [IU]
2022-07-02 02:26:15.887 TRACE 31121 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [5] as [BIGINT] - [1]
Hibernate:
select
song0_.id as id1_9_,
song0_.name as name2_9_,
song0_.price as price3_9_,
song0_.singer as singer1_10_,
song0_.writer as writer2_10_
from
song song0_
Song(super=Product(id=1, name=너의 의미, price=500), singer=IU, writer=IU)
이렇게 song
테이블에만 쿼리가 발생한 것을 확인할 수 있습니다.
이렇게되면 product
테이블은 사용하지 않지만 생성되게 되는데요, Product
클래스를 abstract
로 수정하게되면 테이블은 생성하지 않고 추상 객체로만 사용 가능합니다.
@DiscriminatorColumn
은 테이블 자체가 다르기 때문에 적용할 필요가 없고, 실제로 애너테이션을 추가하더라도 적용되지 않습니다.
여기까지만 보면 아무런 문제가 없어보이는데요, 객체지향적으로 추상클래스만 필요한 상황이 되었다고 가정하고 테스트를 수정해보겠습니다.
package com.tistory.jaimenote.jpa.inheritance.infra.repository;
import com.tistory.jaimenote.jpa.inheritance.entity.Product;
import com.tistory.jaimenote.jpa.inheritance.entity.Song;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.annotation.Rollback;
@DataJpaTest
class ProductRepositoryTest {
@Autowired
ProductRepository productRepository;
@PersistenceContext
EntityManager entityManager;
private Song song;
@BeforeEach
void setup() {
song = Song.create("IU", "IU", "너의 의미", 500);
productRepository.save(song);
entityManager.flush();
entityManager.clear();
}
@Test
@Rollback(false)
void findProductTest() {
Product product = productRepository.findById(song.getId())
.orElseThrow(() -> new IllegalArgumentException("잘못된 ID 입니다."));
System.out.println(product);
}
}
Song
이 Product
의 자식 클래스이기 때문에 ProductRepository
를 이용해 Song
을 저장하였습니다.
앞으로 Movie
와 Book
도 같은 인터페이스를 이용해 저장할 생각에 벌써 싱글벙글 해집니다.
조회할 때도 마찬가지로 ID만 넣어주면 알아서 착착 찾아주겠죠?
테스트를 실행해보겠습니다.
Hibernate:
insert
into
song
(name, price, singer, writer, id)
values
(?, ?, ?, ?, ?)
2022-07-02 02:39:34.869 TRACE 32209 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [너의 의미]
2022-07-02 02:39:34.871 TRACE 32209 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [500]
2022-07-02 02:39:34.872 TRACE 32209 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [IU]
2022-07-02 02:39:34.872 TRACE 32209 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [VARCHAR] - [IU]
2022-07-02 02:39:34.873 TRACE 32209 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [5] as [BIGINT] - [1]
Hibernate:
select
product0_.id as id1_9_0_,
product0_.name as name2_9_0_,
product0_.price as price3_9_0_,
product0_.author as author1_0_0_,
product0_.isbn as isbn2_0_0_,
product0_.actor as actor1_6_0_,
product0_.director as director2_6_0_,
product0_.singer as singer1_10_0_,
product0_.writer as writer2_10_0_,
product0_.clazz_ as clazz_0_
from
( select
id,
name,
price,
null as author,
null as isbn,
null as actor,
null as director,
null as singer,
null as writer,
0 as clazz_
from
product
union
all select
id,
name,
price,
author,
isbn,
null as actor,
null as director,
null as singer,
null as writer,
1 as clazz_
from
book
union
all select
id,
name,
price,
null as author,
null as isbn,
actor,
director,
null as singer,
null as writer,
2 as clazz_
from
movie
union
all select
id,
name,
price,
null as author,
null as isbn,
null as actor,
null as director,
singer,
writer,
3 as clazz_
from
song
) product0_
where
product0_.id=?
insert
문은 예상대로 동작하였지만 select
문이 대환장파티가 되었습니다.
어떻게 보면 가장 객체지향 적인 구조이지만 DB 테이블과는 맞지 않다고 할 수 있습니다.
결론
객체 상속 관계를 테이블과 매핑시키기 위해서는 @Inheritance
애너테이션을 사용하고 attribute
로 상속 전략을 설정할 수 있습니다.
각각의 전략은 장단점이 존재하므로 상황에 맞게 필요한 전략을 선택해서 사용하면 되지만, TABLE_PER_CLASS
전략은 절대 추천하지 않습니다.
데이터의 수가 많지 않을 때는 SINGLE_TABLE
전략이, 데이터가 많고 무결해야 할 경우 JOINED
전략이 유리할 수 있습니다.
'JPA' 카테고리의 다른 글
[JPA] 즉시 로딩(Eager)과 지연 로딩(Lazy) (0) | 2022.07.07 |
---|---|
[JPA] 상속관계 매핑(2): @MappedSuperclass (0) | 2022.07.06 |
[JPA] 다양한 연관관계 매핑 (0) | 2022.07.04 |
[JPA] 연관관계 매핑 예제 (0) | 2022.07.03 |
[JPA] 연관관계의 주인 (0) | 2022.06.30 |
- Total
- Today
- Yesterday
- Spring Boot
- Spring Boot Tutorial
- QueryDSL
- 스프링 부트
- spring boot app
- r
- spring boot jwt
- Java
- spring boot application
- JSON
- leetcode
- Linux
- 헥사고날 아키텍처
- 스프링 부트 애플리케이션
- 스프링부트
- Spring Data JPA
- 알고리즘
- gRPC
- JPA
- @ManyToOne
- 스프링 부트 튜토리얼
- 스프링 데이터 jpa
- Spring Boot JPA
- proto3
- 스프링 부트 회원 가입
- intellij
- 클린 아키텍처
- 함께 자라기
- 함께 자라기 후기
- Jackson
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |