티스토리 뷰
소스 코드는 여기 있습니다. (commit hash: c93b796)
> git clone https://github.com/lcalmsky/jpa > git checkout c93b796Warning: 이번 소스 코드는 최종 커밋 기준으로 작성되어 있어 모든 테스트 결과를 정확히 확인할 순 없으니 참고 부탁드립니다.
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
- 헥사고날 아키텍처
- Linux
- spring boot application
- Java
- proto3
- Jackson
- JSON
- 스프링 부트 회원 가입
- leetcode
- JPA
- 스프링 부트 애플리케이션
- 스프링 부트 튜토리얼
- spring boot jwt
- intellij
- 함께 자라기
- 함께 자라기 후기
- 알고리즘
- Spring Boot
- spring boot app
- 스프링부트
- Spring Boot Tutorial
- 스프링 부트
- Spring Data JPA
- gRPC
- 클린 아키텍처
- r
- Spring Boot JPA
- QueryDSL
- 스프링 데이터 jpa
- @ManyToOne
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 | 31 |
