[JPA 프로그래밍] Entity Mapping 3편[상속관계 매핑]

최동근·2023년 1월 13일
0

JPA

목록 보기
5/13
post-custom-banner

해당 글은 김영한 님의 ["자바 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;
    
}
  • @Inheritance(strategy = InheritanceType.Joined)
    해당 어노테이션은 부모 클래스에 적용하며, strategy 속성을 통해 상송관계 매핑 전략을 명시해줍니다.
  • @DiscriminatrorColumn
    해당 어노테이션은 부모 클래스에 적용하며, DTYPE 칼럼 생성을 위해 사용하는 어노테이션입니다.
    조인 전략은 조인을 사용하기에 DTYPE 을 굳이 사용하지 않아도 알 수 있지만 명확하기 위해 넣어주는 것이 좋습니다.

자식 테이블 중 하나인 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 쿼리가 두개 날라감
  • Insert 쿼리가 2개 나갑니다.
  • Item 테이블, Album 테이블 저장합니다.
  • 구분 칼럼을 ALBUM 으로 지정했기 때문에 자식 테이블 저장시 구분 칼럼으로 ALBUM 값이 들어갑니다.

🧑🏼‍💻 [ 조인 전략 데이터 조회 ]

// 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'
  • 단일 테이블 전략은 부모 테이블을 조회하고 이때 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을 사용하기에 성능 문제가 발생합니다 🥲

이처럼 구현 클래스마다 테이블 전략은 많은 제약과 오류 가능성이 존재하므로 사용을 권장하지 않습니다 🧑🏼‍💻

📕 요약

  • [조인 전략]

    • 장점
      • 테이블 정규화
      • 외래 키 참조 무결성 제약조건 활용 가능
      • 저장 공간 측면에서 효율적(정규화 보장하기 때문)
      • 3가지 전략 중 가장 정석적인 전략임
    • 단점
      • 조회시 조인을 필요로 함
      • 조회 쿼리가 복잡할 수 있음
      • 쿼리가 두번 발생

    

  • [단일 테이블 전략]

    • 장점
      • 조인을 사용하지 않기 때문에 조회 성능이 좋음
      • 조회 쿼리가 단순함
    • 단점
      • 자식 테이블이 가지는 칼럼은 모두 Null 허용함
      • 하나의 테이블에 모든 정보를 저장하기 때문에 테이블이 너무 커질 수가 있음
      • 조인이 단순한것보다 테이블 하나가 너무 커져서 성능 저하가 올 수 있음

  • [ 구현 클래스마다 테이블 전략 ]

    • 장점

      • 자식 타입을 명확하게 구분해서 처리 가능
      • NOT NULL 제약 조건 사용 가능
    • 단점

      • 여러 자식 테이블을 함께 조회시 Union 사용 -> 성능 저하
      • 통합해서 쿼리하기 어려움
      • 권장되지 않는 전략

참고

[JPA] 상속관계 매핑 전략(@Inheritance, @DiscriminatorColumn)
[JPA] 3가지 상속 관계 Mapping 전략

profile
비즈니스가치를추구하는개발자
post-custom-banner

0개의 댓글