
이번 강의 챕터에서는 상속 관계 매핑과 관련된 내용을 학습했다. 처음에는 RDB에 상속 개념이 없는데 이게 가능한가? 생각했지만 강의를 보면서 "아~" 라는 감탄사와 함께 이해가 되었다. 정리해보자. ⭐️

상속 관계 매핑이란 객체의 상속 구조와 DB의 슈퍼타입 및 서브타입 관계를 매핑하는 것을 의미한다. RDB에서 슈퍼타입 및 서브타입 모델을 구현하는 방법은 총 3가지가 있다. (생각해보면 그저 정확한 명칭을 몰랐을 뿐, 내가 예전에 한 번씩 사용했봤던 내용이다. ㅎㅎ 😂)
상속 관계 매핑에서 사용되는 주요 어노테이션은 다음과 같다. (나는 직접 어노테이션 코드를 열어서 확인해봤다! 😤)
@Inheritance(strategy = InheritanceType.xxx) : 상속 관계 전략 지정 (기본 단일 테이블)@DiscriminatorColumn(name = "DTYPE") : 상위 테이블의 DTYPE 컬럼명 지정 (기본 DTYPE)@DiscriminatorValue(value = "xxx") : 상위 테이블에 저장될 DTYPE 값 지정 (기본 엔티티)조인 전략은 말 그대로 조인을 이용한 방법이다. 가장 정석적인 방법이며, 객체의 상속 관계를 RDB에서도 표현한 방법으로 봐도 될 것 같다.

@Entity
@Table(name = "ITEM")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "d_type")
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private int price;
// Getter, Setter 생략
@Entity
@Table(name = "ALBUM")
@DiscriminatorValue(value = "A")
public class Album extends Item {
private String artist;
// Getter, Setter 생략
@Entity
@Table(name = "MOVIE")
@DiscriminatorValue(value = "M")
public class Movie extends Item {
private String director;
private String actor;
// Getter, Setter 생략
@Entity
@Table(name = "BOOK")
@DiscriminatorValue(value = "B")
public class Book extends Item {
private String author;
private String isbn;
// Getter, Setter 생략


테이블이 생성되는 쿼리를 확인해보면 아이템과 아이템의 PK를 가진 앨범, 영화, 책 테이블이 새로 생성되는 것을 확인할 수 있다. 그럼 데이터를 저장하고 조회할 때는 어떻게 동작할까?
try {
// ITEM, MOVIE 테이블에 데이터 저장
Movie movie = new Movie();
movie.setDirector("A");
movie.setActor("B");
movie.setName("바람과함께사라지다");
movie.setPrice(10000);
entityManager.persist(movie);
entityManager.flush();
entityManager.clear();
// 조인을 이용한 조회
Movie findMovie = entityManager.find(Movie.class, movie.getId());
System.out.println("findMovie = " + findMovie);
entityTransaction.commit();
} catch (Exception e) {
entityTransaction.rollback();
} finally {
entityManager.close();
}
entityManagerFactory.close();


코드처럼 영화에 대한 데이터를 저장할 때는 아이템과 영화 테이블에 INSERT문 2개가 전달되고, 조회할 때는 아이템과 영화 테이블을 조인해서 정보를 가져오는 것을 확인할 수 있다.
동작 과정을 통해서 알 수 있는 정보들은 다음과 같다.
단일 테이블 전략은 하나의 테이블에 모두 때려넣는 방법이다. 위의 조인 전략 코드에서 @Inheritance 어노테이션의 속성값만 변경해주면 되기에 코드는 생략하자. 😋
참고로 단일 테이블 전략에서는 @DiscriminatorColumn 어노테이션이 없어도 동작한다. 하나의 테이블에서 구분할 수 있는 기준이 반드시 필요하기 때문에 자동으로 등록해주기 때문이다.

단 하나의 테이블로 운영한다는 점에서 알 수 있는 정보들은 다음과 같다.
클래스별 테이블 전략은 각각의 테이블로 아예 분리시켜 놓은 방법이다. @Inheritance 어노테이션의 속성값만 변경해주면 되며, 각 테이블이 독립적으로 만들어지기 때문에 구분 기준인 @DiscriminatorColumn 어노테이션이 불필요해진다.

클래스별 테이블 전략의 문제점을 코드로 확인해보자.
try {
// ITEM, MOVIE 테이블에 데이터 저장
Movie movie = new Movie();
movie.setDirector("A");
movie.setActor("B");
movie.setName("바람과함께사라지다");
movie.setPrice(10000);
entityManager.persist(movie);
entityManager.flush();
entityManager.clear();
// 부모 클래스 타입으로 조회
Item findItem = entityManager.find(Item.class, movie.getId());
System.out.println("findItem = " + findItem);
entityTransaction.commit();
} catch (Exception e) {
entityTransaction.rollback();
} finally {
entityManager.close();
}
entityManagerFactory.close();

상속 관계에서 상위 클래스 타입으로 조회를 할 경우 UNION으로 엮인 쿼리가 전달되는 것을 확인할 수 있다. 독립적으로 구성된 테이블이기 때문에 원하는 데이터를 찾으려면 떨어져있는 테이블들을 모두 합쳐서 찾아야하기 때문이다. (물론 특정 테이블을 명시하여 조회를 할 경우에는 문제가 없다!)
이 전략의 특징은 다음과 같다.
김영한님은 주로 조인 전략을 이용하시지만, 비즈니스적으로 복잡하지 않고 데이터가 단순한 형태일 경우에는 단일 테이블 전략을 사용한다고 하셨다. (클래스별 테이블 전략은 아예 거들떠보지도 않으신다고 하셨다. ㅎㅎ)