해당 글은 김영한 님의 ["자바 ORM 표준 JPA 프로그래밍"] 을 스터디 하면서 정리하는 글 입니다 !👨💻
📣 이번 포스팅은 Entity Mapping 2편 에 이어 상속관계 매핑에 대해 알아보겠습니다.
객체지향 프로그래밍에 있어 상속은 꼭 알아야할 중요한 개념입니다.
JPA 는 Java 의 표준 ORM 기술이며 자바의 객체를 테이블로 매핑시켜주는 역할을 하는데 여기서 패러다임의 불일치로 인해 문제가 발생합니다 ⛔️
바로 테이블에는 상속의 개념이 없디 때문인데요 😥
데이터베이스에 관련된 모델링 기법에서 그나마 상속과 비슷한 개념인 슈퍼타입 - 서브타입 관계가 존재합니다.
결론적으로 객체의 상속 관계를 데이터베이스의 슈퍼타입 - 서브타입 관계로 매핑하는 것을 상속관계 매핑이라고 합니다 👨💻
상속관계 매핑에는 3가지 전략이 있습니다.
앞으로 나올 각 전략 설명을 위해 예제를 준비했습니다 👼
public abstract class Item { // 부모 클래스
private Long id;
private String name;
private Long price;
...
}
public class Album extends Item { // 자식 클래스
private Long id;
private String name;
private Long price;
private Artist artist;
}
public class Movie extends Item { // 자식 클래스
private Long id;
private String name;
private Long price;
private Director director;
private Actor actor;
}
public class Book extends Item { // 자식 클래스
private Long id;
private String name;
private Long price;
private Author author;
private String isbm;
}
조인 전략은 가장 정규환 된 방법으로 구현하는 방식입니다 ❗️
조인 전략의 특징은 엔티티 각각을 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아
기본 키 + 외래 키 로 사용하는 전략입니다.
자식 테이블 중 어느 테이블을 조회해야하는지 구분하기 위해 DTYPE 구분 컬럼을 사용합니다 ✏️
해당 이미지를 보면 부모 테이블(Item)의 Pk 인 Item_ID 를 각 자식 테이블이 Pk + Fk 로 사용하고 있다는 것을 확인 할 수 있습니다.
또한, 자식 테이블에서 공통적으로 가지는 칼럼인 price 와 name은 부모 테이블에 저장되고 자식 테이블에서만 가지는 칼럼은 각자의 테이블에만 저장되는 것을 볼 수 있습니다.
조인 전략을 실제 적용한 코드를 보겠습니다.
@Inheritance(strategy = InheritanceType.JOINED) // 조인 전략을 사용하겠다.
@DiscriminatorColumn // 자식 테이블의 구분 칼럼(default = DTYPE)
@Entity
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Long price;
}
자식 테이블 중 하나인 Album 클래스를 살펴보겠습니다.
@DiscriminatorValue("ALBUM")
@PrimaryKeyJoinColumn(name = "Album_ID")
@Entity
public class Album extends Item{
private String artist;
}
@DiscriminatorValue("ALBUM")
구분 칼럼 지정, Default 값으로 엔티티 이름 사용
부모 테이블의 @DiscriminatorColumn 이였으므로, 구분 칼럼 이름의 default 값인 DTYPE 이 적용되었으며, Album 테이블에서 해당 테이블에 대한 구분 칼럼의 값을 ALBUM 라고 했기에 DTYPE = ALBUM 로 설정되었습니다.
default 값으로 엔티티 이름을 사용합니다.
@PrimaryKeyJoinColumn(name = "Album_ID")
조인 칼럼 전략에 따라 부모 테이블의 기본 키를 자식 테이블에서 기본 키로 그대로 사용하나,
해당 어노테이션으로 기본 키 이름 지정할 수 있습니다.
조인 전략은 이름에서도 알 수 있듯이 조인을 이용하여 데이터를 조회하고 저장시에는 부모 클래스, 자식 클래스 2개의 쿼리가 생성됩니다.
코드를 통해 더 자세히 살펴보겠습니다 👼
👨💻 [ 조인 전략 데이터 저장 ]
// Album 데이터를 저장하는 코드
Album album = new Album();
album.setArtist("백예린");
album.setPrice(20000);
album.setName("사랑");
em.persist(album); // 영속화
tx.commit(); // 트랜잭션 커밋
// Album 데이터를 저장할 때 날라가는 코드
Hibernate :
insert
into
item
(id,name,price,DTYPE)
values
(null,?,?,'ALBUM')
Hibernate :
insert
into
album
(artist, album_id,)
values
(?,?,?)
// Item, Album 테이블에 저장
// Insert 쿼리가 두개 날라감
🧑🏼💻 [ 조인 전략 데이터 조회 ]
// Album 데이터를 저장하는 코드
Album album = new Album();
album.setArtist("백예린");
album.setPrice(20000);
album.setName("사랑");
em.persist(album); // 영속화
em.flush();
em.clear(); // 1차 캐시 삭제
em.find(Album.class, album.getId());
tx.commit();
// Album 데이터를 조회할 때 날라가는 코드
Hibernate:
select
~~
from Album album0_
inner join
Item album0_1_
on album0_.id = album0_1_.id
where
album0_.id = ?
// inner join 을 통해 조회
단일테이블 전략은 이름에서 알 수 있는 것처럼 '하나의 테이블'을 통해 상속관계 매핑을 합니다 👨💻
즉, 모든 자식 테이블 부모 테이블에 포함하여 DTYPE으로 자식 테이블을 구분합니다.
해당 이미지는 단일 테이블 전략을 도식화 한 이미지입니다.
모든 자식 테이블의 칼럼이 하나의 부모 테이블에 존재하는 것이 보이시나요?
단일 테이블 전략은 앞에서 보았던 조인 전략과는 달리 조인을 사용하지 않으므로 모든 쿼리가 한 번만 날라갑니다 💪
@Inheritance(strategy = InheritanceType.SINGLE_TABLE // 단일 테이블 전략을 사용하겠다.
@DiscriminatorColumn // 자식 테이블의 구분 칼럼(default = DTYPE)
@Entity
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Long price;
}
@DiscriminatorValue("ALBUM")
@PrimaryKeyJoinColumn(name = "Album_ID")
@Entity
public class Album extends Item{
private String artist;
}
저희는 여기서 JPA 의 장점을 파악할 수 있습니다 ❗️
어노테이션의 변경(InheritanceType.JOINED -> InheritanceType.SINGLE_TABLE) 을 통해 테이블 구조의 변경이 가능합니다.
만약 JPA를 사용하지 않았다면 모든 코드를 손대야 변경이 가능합니다 🙆🏻
🧑🏼💻 [ 단일 테이블 전략 데이터 조회 ]
Album 테이블을 조회할 때 날라가는 코드
Hibernate
select
~~~
from
Item album0_
where
album0_.id = ?
and album0_.DTYPE = 'ALBUM'
구현 클래스마다 테이블 전략은 이름에서 알 수 있듯이 자식 엔티티 마다 모든 테이블을 생성하는 전략입니다.
결론만 말하자면 권장되지 않는 방식입니다.
해당 이미지는 구현 클래스마다 테이블 전략을 도식화한 이미지입니다.
이미지에서 볼 수 있듯이, 어떠한 칼럼도 공유하지 않은채로 서로의 테이블을 가지는 형태를 취합니다 👨💻
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS // 구현 클래스마다 테이블 전략을 사용하겠다.
@DiscriminatorColumn // 자식 테이블의 구분 칼럼(default = DTYPE)
@Entity
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Long price;
}
@DiscriminatorValue("ALBUM")
@PrimaryKeyJoinColumn(name = "Album_ID")
@Entity
public class Album extends Item{
private String artist;
구현 클래스마다 전략은 조인 전략과 비슷합니다.
하지만 부모타입의 칼럼(name,price) 을 자식 타입으로 모두 내립니다. 즉 중복을 허용합니다.
중복을 허용하기에 DTYPE 도 필요가 없습니다.
또한, 해당 전략은 기본키 생성 전략 중 GenerationType.IDENTITY인 경우
에러가 발생합니다. 즉 IDENTITY (테이블에 기본키 생성 위임)을 사용하지 못하므로 많은 제약이 있습니다.
그리고 여러 자식 테이블을 함께 조회시 UNION을 사용하기에 성능 문제가 발생합니다 🥲
이처럼 구현 클래스마다 테이블 전략은 많은 제약과 오류 가능성이 존재하므로 사용을 권장하지 않습니다 🧑🏼💻
[조인 전략]
[단일 테이블 전략]
[ 구현 클래스마다 테이블 전략 ]
장점
단점
[JPA] 상속관계 매핑 전략(@Inheritance, @DiscriminatorColumn)
[JPA] 3가지 상속 관계 Mapping 전략