728x90
반응형
SMALL
상속관계 매핑
- 객체는 상속관계가 존재하지만, 관계형 DB에는 상속 관계가 없다.
- 상속관계 매핑이라는 것은 객체의 상속 구조와 DB의 슈퍼타입 서브타입 관계를 매핑하는 것
슈퍼타입과 서브타입 논리 모델 → 물리모델로 구현하는 방법
- 3가지 방법이 존재한다.
- JPA가 이 3가지 방법과 매핑하려면 @Inheritence(strategy=InheritanceType.XXX)의 strategy를 설정해주면 된다.
- XXX = ‘JOINED’,’SINGLE_TABLE’,’TABLE_PER_CLASS’로 구성되어 있다.
- @DiscriminatorColumn(name=”DTYPE”) 부모 클래스에서 선언, 하위 클래스를 구분하는 용도의 컬럼을 생성 관례는 default=DTYPE
- @DiscriminatorValue(”XXX”)하위 클래스에서 선언한다. 엔티티를 저장할 떄 슈퍼타입의 구분 컬럼에 저장할 값을 지정한다. 어노테이션을 선언하지 않을 경우 기본값으로 클래스 이름이 들어간다.
각 객체 코드 구현
//Item
@Entity
@Inheritance(strategy = InheritanceType.XXX) // 상속 구현 전략 선택
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int price;
}
//Album
@Entity
public class Album extends Item {
private String artist;
}
//Movie
@Entity
public class Movie extends Item {
private String director;
private String actor;
}
//Book
@Entity
public class Book extends Item {
private String author;
private String isbn;
}
조인 전략
- 하위 테이블을 나누고 join으로 가져오는 구성 , 구분은 상위 테이블에서 DTYPE으로 구분한다.
- 가장 정규화된 방식하이버네티의 조인 전략에서는 @DiscirminatorColumn을 선언하지 않으면 DTYPE컬럼이 생성되지 않는다.
- 어차피 조인하면 앨범인지 무비인지는 알 수 있지만 그래도 DTYPE을 넣어주는 것이 명확하다.
@Entity @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn// 하위 테이블의 구분 컬럼 생성 기본값은 DTYPE public class Item { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private int price; }
실행된 DDL구문
- 하위 테이블에서는 Item_id를 PK이면서 FK로 참조한다.
Hibernate:
create table Album (
artist varchar(255),
id bigint not null,
primary key (id)
)
Hibernate:
create table Book (
author varchar(255),
isbn varchar(255),
id bigint not null,
primary key (id)
)
Hibernate:
create table Item (
DTYPE varchar(31) not null,
id bigint generated by default as identity,
name varchar(255),
price integer not null,
primary key (id)
)
Hibernate:
create table Movie (
actor varchar(255),
director varchar(255),
id bigint not null,
primary key (id)
)
//조인 전략에 맞는 제약조건이 생성된다.
Hibernate:
alter table Album
add constraint FKcve1ph6vw9ihye8rbk26h5jm9
foreign key (id)
references Item
Hibernate:
alter table Book
add constraint FKbwwc3a7ch631uyv1b5o9tvysi
foreign key (id)
references Item
Hibernate:
alter table Movie
add constraint FK5sq6d5agrc34ithpdfs0umo9g
foreign key (id)
references Item
이중 하나의 객체를 저장하면?
Book객체 생성
@Test
@Transactional
public void CheckInheritance(){
Book book = new Book();
book.setName("개미");
book.setAuthor("베르나르베르베르");
book.setPrice(10000);
book.setIsbn(1234);
em.persist(book);
}
DDL 결과 구문
- 쿼리가 두 개 나간다. 이 부분은 지연로딩을 해도 두개가 나간다.
- DTYPE은 내가 설정한 ‘B’로 나가는걸 볼 수 있다.
*/ insert
into
item
(name, price, dtype, item_id)
values
(?, ?, 'B', ?)
insert into item (name, price, dtype, item_id) values (?, ?, 'B', ?)
insert into item (name, price, dtype, item_id) values ('개미', 10000, 'B', 1);
insert
into
book
(author, isbn, item_id)
values
(?, ?, ?)
- 단일 테이블 : 모든 컬럼을 다 때려박는다. DTYPE으로 해당 테이블이 어떤 테이블인지 구분한다.
- 성능때문에 고려하기도 한다.
하나의 객체를 조회하면?
Book객체 조회
@Test
@Transactional
public void CheckInheritance(){
Book book = new Book();
book.setName("개미");
book.setAuthor("베르나르베르베르");
book.setPrice(10000);
book.setIsbn(1234);
em.persist(book);
em.flush();
em.clear();
Book result = em.find(Book.class, book.getId());
}
DDL 구문
- inner Join을 통해 조회하는 것을 확인할 수 있다.
select
book0_.item_id as item_id2_3_0_,
book0_1_.name as name3_3_0_,
book0_1_.price as price4_3_0_,
book0_.author as author1_1_0_,
book0_.isbn as isbn2_1_0_
from
book book0_
inner join
item book0_1_
on book0_.item_id=book0_1_.item_id
where
book0_.item_id=?
단일 테이블 전략
- 서비스 규모가 크지 않고, 굳이 조인 전략을 선택해서 복잡하게 갈 필요가 없다고 판단 될 때에는 한 테이블에 다 저장하고, DTYPE으로 구분하는 단일 테이블 전략을 선택할 수 있다.
- INSERT 쿼리도 한 번, SELECT 쿼리도 한 번에 나간다.
- Strategy를 SINGLE_TABLE로 적용하면 된다.
- 한 테이블의 모든 컬럼을 저장하기 때문에, DTYPE없이는 테이블을 판단할 수 없다.
JPA의 장점
- 테이블 구조의 변동이 일어났는데, 코드를 거의 손대지 않고 어노테이션만 수정했다.
- 만약 JPA를 사용하지 않았더라면, 테이블의 구조의 변경이 일어나면 거의 모든 코드를 손대야 했다.
단일 테이블 전략 코드 구현
package study.jpa.queryDSL.domain.item;
import lombok.Data;
import javax.persistence.*;
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Data
@DiscriminatorColumn(name = "DTYPE")
public class Item {
@Id @GeneratedValue
@Column(name = "item_id")
private Long id;
private String name;
private int price;
}
//Test코드
@Test
@Transactional
public void CheckInheritance(){
Book book = new Book();
book.setName("개미");
book.setAuthor("베르나르베르베르");
book.setPrice(10000);
book.setIsbn(1234);
em.persist(book);
em.flush();
em.clear();
Book result = em.find(Book.class, book.getId());
}
DDL 구문
//insert
insert
into
item
(name, price, author, isbn, dtype, item_id)
values
(?, ?, ?, ?, 'B', ?)
2023-12-09 04:37:01.029 TRACE 5240 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [개미]
2023-12-09 04:37:01.029 TRACE 5240 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [10000]
2023-12-09 04:37:01.029 TRACE 5240 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [베르나르베르베르]
2023-12-09 04:37:01.029 TRACE 5240 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [INTEGER] - [1234]
2023-12-09 04:37:01.029 TRACE 5240 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [5] as [BIGINT] - [1]
.//select
select
book0_.item_id as item_id2_1_0_,
book0_.name as name3_1_0_,
book0_.price as price4_1_0_,
book0_.author as author6_1_0_,
book0_.isbn as isbn7_1_0_
from
item book0_
where
book0_.item_id=?
and book0_.dtype='B'
서브타입 테이블 전략
- 조인 전략과 유사하지만, 슈퍼 타입의 컬럼들을 서브 타입으로 내린다.
- 상위 테이블의 컬럼들이 중복되도록 허용하는 구조
- 구현 클래스마다 테이블 생성 전략을 적용한다.
상속관계 매핑 정리
조인 전략
- 장점
- 테이블이 정규화되어 있다
- 외래 키 참조 무결성 제약조건을 활용 가능하다
- 다른 테이블들이 하나의 상위 테이블만 바라보도록 설계하는 것이 가능하다.
- 저장 공간이 효율화된다. 정규화로 필요한 만큼만 소비된다.
- 단점
- 조회 시 조인을 많이 사용한다.
- 단일 테이블 전략에 비하면 성능이 안나온다.
- 쿼리가 복잡하다
- 데이터 저장시 INSERT 쿼리가 사우이, 하위 테이블 두 번 발생한다.
- 정리
- 성능 저하라고 했지만 크게 영향이 있지는 않다.
- 오히려 저장공간이 효율화 되기 때문에 장점이 크다.
- 기본적으로는 조인 전략이 정석적
단일 테이블 전략
- 장점
- 조인이 필요 없으므로 성능이 빠르다
- 조회 쿼리가 단순하다
- 단점
- 자식 엔티티가 매핑한 컬럼은 모두 NULL을 허용
- 단일 테이블에 모든 것을 저장하므로 테이블이 커진다.
- 상황에 따라 조인 전략과 교체하며 사용해야 한다.
클래스 별 테이블 전략
- 잘 사용하지 않는다.
- ORM을 사용하면 데이터와 객체 사이 TRADE OFF가 발생한다.
- 장점
- 서브 타입을 명확하게 구분해서 처리할 때 효과적
- NOT NULL 제약조건이 간으하다
- 단점
- 여러 자식 테이블을 함께 조회할 떄 성능이 느리다(UNION ALL)
- 자식 테이블을 통합해서 쿼리하기 어렵다
- 만약 ITEM의 클래스에 값이 변경되면 모든 클래스에 영향을 미친다.
결론
- 정석은 조인 전략
- 조인 전략과 단일 전략의 성능을 고려하며 선택
- 매우 단순하고 확장할 가능성이 적으면 단일 전략
- 비즈니스 적으로 중요하고, 복잡하며, 확장될 확률이 높으면 조인 전략
728x90
반응형
SMALL
'Spring > JPA' 카테고리의 다른 글
@Where Deprecated되고 새로 쓰이는 @SQLRestriction (1) | 2024.11.20 |
---|---|
JPA - DB 연결 예외 : 'url' attribute is not specified and no embedded datasource could be configured. (0) | 2024.06.14 |
JPA - 지연로딩과 즉시로딩 (1) | 2023.12.10 |
JPA - Proxy (2) | 2023.12.10 |
JPA - MappedSuperClass (0) | 2023.12.10 |