JPA 게시글은 대부분 인프런의 김영한님의 강의인 '자바 ORM 표준 JPA 프로그래밍' 기반으로 내용을 정리했습니다.
상속 관계 매핑
- 관계형 데이터베이스에는 상속 관계라는 개념이 존재하지 않는다.
- 대신 슈퍼타입 서브타입 관계라는 모델링 기법이 객체 상속과 그나마 유사하다.
- 상속 관계를 매핑 : 객체의 상속 구조와 DB의 슈퍼타입 서브타입 관계를 매핑
슈퍼타입 서브타입 논리 모델 -> 물리 모델로 구현
조인 전략
각각의 테이블로 만들어 JOIN으로 데이터를 뽑아오는 방식이다. ITEM이라는 상위 테이블을 만들고, ALBUM에 관한 데이터를 가져오고 싶으면 ITEM과 ALBUM을 JOIN하여 데이터를 가져온다.
- 장점
- 테이블 정규화가 가장 잘 되어있다.
- 외래키 참조 무결성 제약조건 활용이 가능하다.
- 저장공간이 효율적이다.
- 단점
- 조회 시 조인을 많이 사용하게 되어 성능 저하로 이어진다.
- 조회 쿼리가 복잡하다.
- 데이터 저장 시 INSERT SQL가 두 번 생성된다.
- 객체 클래스 생성
// Item.class
@Entity
@Inheritance(strategy = InheritanceType.JOINED) // **
public class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
public Long getId() {
return id;
}
...getter/setter 생략
}
//Movie.class
@Entity
public class Movie extends Item{
private String director;
private String actor;
...getter/setter 생략
}
//Book.class
@Entity
public class Book extends Item {
private String author;
private String isbn;
...getter/setter 생략
}
//Album.class
@Entity
public class Album extends Item{
private String artist;
...getter/setter 생략
}
부모 클래스에 '@Inheritance(strategy = InheritanceType.JOINED)' 애노테이션을 주면 조인 전략으로 매핑된다.
// 출력 로그
Hibernate:
create table Item (
id bigint not null,
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:
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)
)
- DiscriminatorColumn
Item 테이블에는 자세히보면 DTYPE이 존재한다. DTYPE을 생성하려면 DiscriminatorColumn 애노테이션으로 설정해주면 된다.
//Item.class
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public class Item {
... 생략
// 출력 로그
create table Item (
DTYPE varchar(31) not null, // DTYPE 추가
id bigint not null,
name varchar(255),
price integer not null,
primary key (id)
)
DiscriminatorColumn 애노테이션을 설정해주면 INSERT SQL이 실행될 때 적절한 값을 알아서 잘 넣어준다. 참고로 기본값은 DTYPE이므로 그냥 @DiscriminatorColumn만 적어줘도 된다.
// 실행 클래스에서 Movie 관련된 INSERT SQL 생성 시
/* insert hellojpa.Movie
*/ insert
into
Item
(name, price, DTYPE, id)
values
(?, ?, 'Movie', ?)
- DiscriminatorValue
구분 컬럼에 입력할 값을 지정한다. 이 애노테이션을 지정하지 않을 시 기본값은 엔티티 이름이다.
// Movie.class
@Entity
@DiscriminatorValue("A") // DTYPE에 A로 저장된다.
public class Movie extends Item{
... 생략
- 조인 전략은 em.persist()할 때 INSERT SQL 두 번 실행, em.find() 할 때 SELECT 문에서 JOIN이 이루어진다.
// 실행 클래스
...생략
Movie movie = new Movie();
movie.setDirector("who");
movie.setActor("are");
movie.setName("you");
movie.setPrice(10000);
em.persist(movie);
em.flush();
em.clear();
Movie findMovie = em.find(Movie.class, movie.getId()); // SELECT JOIN
System.out.println("findMove = " + findMovie);
...생략
// 출력 로그
//INSERT
Hibernate:
/* insert hellojpa.Movie
*/ insert
into
Item
(name, price, DTYPE, id)
values
(?, ?, 'Movie', ?)
Hibernate:
/* insert hellojpa.Movie
*/ insert
into
Movie
(actor, director, id)
values
(?, ?, ?)
// SELECT
Hibernate:
select
movie0_.id as id2_2_0_,
movie0_1_.name as name3_2_0_,
movie0_1_.price as price4_2_0_,
movie0_.actor as actor1_5_0_,
movie0_.director as director2_5_0_
from
Movie movie0_
inner join
Item movie0_1_
on movie0_.id=movie0_1_.id
where
movie0_.id=?
findMove = hellojpa.Movie@39aa45a1
단일 테이블 전략
단일 테이블 전략은 그냥 Album, Movie, Book 테이블에 존재하는 모든 컬럼을 ITEM 테이블에 다 집어 넣는 방법이다.
- 장점
- 조인이 필요없으므로 조회 성능이 빠르다.
- 조회 쿼리가 단순하다.
- 단점
- 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야한다.
- Album에 관련한 데이터를 저장할 경우 작곡가만 필요하고 나머지 감독, 배우, 작가, ISBN은 필요가 없으므로 null 허용을 줄 수 밖에 없다.
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 상황에 따라서 오히려 조회 성능이 느려질 수 있다.
- 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야한다.
- 단일 테이블 전략 구현
조인 전략에서 작성했던 Item 클래스 코드 한 줄만 아래와 같이 변경하면 된다.
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
// 출력 로그
Hibernate:
create table Item (
DTYPE varchar(31) not null,
id bigint not null,
name varchar(255),
price integer not null,
artist varchar(255),
author varchar(255),
isbn varchar(255),
actor varchar(255),
director varchar(255),
primary key (id)
)
- @DiscriminatorColumn이 없어도 DTYPE이 알아서 들어간다.
//Item.class
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
//@DiscriminatorColumn
public class Item {
...생략
// 출력 로그
Hibernate:
create table Item (
DTYPE varchar(31) not null, // 알아서 추가된다.
id bigint not null,
name varchar(255),
price integer not null,
artist varchar(255),
author varchar(255),
isbn varchar(255),
actor varchar(255),
director varchar(255),
primary key (id)
)
구현 클래스마다 테이블 전략
구현 클래스마다 테이블을 생성하는 전략이다. 즉 ITEM 테이블을 없애고 Albnm, Movie, Book 테이블에 ITEM 속성을 각각 집어넣어 중복을 감안한다.
- 이 전략은 DB 설계자와 ORM 전문가 둘 다 추천하지 않는다.
- 장점
- 서브 타입을 명확하게 구분해서 처리할 때 효과적이다.
- not null 제약 조건을 사용할 수 있다.
- 단점
- 여러 자식 테이블과 함께 조회할 때 성능이 느리다.(UNION SQL 필요)
- 자식 테이블을 통합해서 쿼리하기 어렵다.
- 구현 클래스 구현
Item 클래스의 Inheritance 애노테이션을 수정하고 Item 클래스를 추상 클래스로 변경한다. 추상 클래스가 아닐 시 Item의 테이블까지 같이 만들어진다. 즉 Item을 상속과 상관없이 독단적으로 사용할 수 있게 된다. (원래는 처음부터 추상 클래스로 만들었어야함이 맞다.)
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item { // abstract 추가
... 생략
// 출력 로그
create table Album (
id bigint not null,
name varchar(255),
price integer not null,
artist varchar(255),
primary key (id)
)
Hibernate:
create table Book (
id bigint not null,
name varchar(255),
price integer not null,
author varchar(255),
isbn varchar(255),
primary key (id)
)
Hibernate:
create table Movie (
id bigint not null,
name varchar(255),
price integer not null,
actor varchar(255),
director varchar(255),
primary key (id)
)
Item 테이블은 생성되지 않았다.
* 위의 세가지 중 어떤 방식으로 DB를 구성하든 JPA에서 객체는 매핑을 할 수 있다.