본문 바로가기

Dev/JPA

RS) 상속관계 매핑

테이블에서의 상속관계와 객체에서의 상속관계

  • 관계형 데이터베이스는 상속관계가 없다.
  • 슈퍼타입 서브타입 관계라는 모델링 기법이 객체에서의 상속이랑 유사

디비 입장에서 객체에서의 상속관계처럼 만들 수 있는 여러가지 전략이 있다.

  • 조인전략(JOINED)
    조인전략(JOINED)
    • @Inheritance(strategy = InheritanceType.JOINED)
    • @DiscriminatorColumn 어노테이션을 통해 DTYPE을 넣어줄 수 있고 위의 그림에서 ALBUM, MOVIE, BOOK의 엔티티 명이 Default로 들어간다.
    • 위의 컬럼은 안넣어줘도 상관없지만, 넣어주는 것이 좋다. (DTYPE이 ALBUM때매 들어온건지 MOVIE때문인지 등을 분별할 수 있기때문에)
    • 자식들에는 @DiscriminatorValue를 써줘야 한다. (회사마다 다르겠지만, DBA의 특성에 따라 Default인 엔티티명을 그대로 쓰는것이 아니라 별칭을 쓸수도 있기 때문 ex) ALBUM이 아니라 DTYPE을 A로 표시할 경우 @DiscriminatorValue("A")
    • 기본적으로는 JOINED 전략을 쓰겠다 라고 생각을 가지고 가야 한다. 
      • 장점
        1. 테이블 정규화
        2. 외래키 참조 무결성 제약조건 활용가능
        3. 저장공간 효율화
      • 단점
        1. 조회시 조인을 많이 사용, 성능 저하
        2. 조회 쿼리가 복잡함
        3. 데이터 저장시 INSERT 쿼리 2번 호출
    • 실행 결과
      Hibernate: 
          /* insert hellojpa.Movie
              */ insert 
              into
                  Item
                  (name, price, DTYPE, id) 
              values
                  (?, ?, 'M', ?)
      Hibernate: 
          /* insert hellojpa.Movie
              */ insert 
              into
                  Movie
                  (actor, director, id) 
              values
                  (?, ?, ?)
      
      // JPA가 JOINED 전략이면 알아서 판단해서
      // INNER JOIN 하여 값을 가져온다.
      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_6_0_,
              movie0_.director as director2_6_0_ 
          from
              Movie movie0_ 
          inner join
              Item movie0_1_ 
                  on movie0_.id=movie0_1_.id 
          where
              movie0_.id=?
  • 단일 테이블 전략(SINGLE_TABLE)
    단일 테이블 전략(SINGLE_TABLE)

    • @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    • 단일 테이블 전략에서는 @DiscriminatorColumn 이 들어가있지 않아도 알아서 DTYPE이 생긴다.
      • 단일 테이블 전략에서는 한테이블에 들어가있다 보니까 이게 ALBUM인지 MOVIE인지 등의 엔티티를 판별할 수 없기때문에 DTYPE이 필수적으로 있어야 한다.
        • 장점
          1. 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
          2. 조회 쿼리가 단순함
        • 단점
          1. 자식 엔티티가 매핑한 컬럼은 모두 null 허용
            • 내가 선택한 자식 테이블의 데이터를 제외하고 다른 테이블에도 null 허용이 되어야 한다.
            • ex) BOOK 테이블에 있는 작가(Author) 값만 넣어줘야 하는데, 한테이블에 다른 자식인 ALBUM이나 MOVIE도 있기 때문에 얘네들이 들고있는 컬럼을 null 허용을 해줘야 한다는 뜻
          2. 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있음 상황에 따라서 조회 성능이 느려질 수 있다.
    • 실행결과
      // SINGLE_TABLE 전략으로 설정하게되면
      // JPA가 아래와 같이 하나의 테이블에다가 쫙 다 넣어준다.
      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)
          )
      
      // INSERT 할 경우에도 한번에 쫙 INSERT 한다.
      Hibernate: 
          /* insert hellojpa.Movie
              */ insert 
              into
                  Item
                  (name, price, actor, director, DTYPE, id) 
              values
                  (?, ?, ?, ?, 'M', ?)
      Hibernate: 
          select
              movie0_.id as id2_0_0_,
              movie0_.name as name3_0_0_,
              movie0_.price as price4_0_0_,
              movie0_.actor as actor8_0_0_,
              movie0_.director as director9_0_0_ 
          from
              Item movie0_ 
          where
              movie0_.id=? 
              and movie0_.DTYPE='M'
  • 구현 클래스마다 테이블 전략(TABLE_PER_CLASS)
    구현 클래스마다 테이블 전략(TABLE_PER_CLASS)
    • @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
    • 테이블 구현할때마다 각각 데이터를 다 들고있는 전략
      • 결과적으로 봤을 때 세번째 전략인 구현 클래스마다 테이블 전략은 사용하지 말자! 
      • 명확하게 특정 테이블에서 데이터를 찾는 것은 쉽지만, 단점이 특정 아이디를 가지고 조회할 경우에 각 테이블을 다 뒤져봐야한다는 단점이 있다.
        • 장점
          1. 서브 타입을 명확하게 구분해서 처리할 때 효과적
          2. not null 제약조건 사용 가능
        • 단점
          1. 여러 자식 테이블을 함께 조회할 때 성능이 느림 (UNION 쿼리를 쓰기 때문)
          2. 자식 테이블을 통합해서 쿼리하기 어려움
          3. 변경이라는 관점으로 봤을 떄 정말 비효율적이다 새로운 타입이 추가가 될 경우 많은 부분을 뜯어야한다.
    • 실행결과
      // 각 테이블별로만 만들어지고 ITEM 테이블은 만들어지지 않는다.
      // 부모테이블이 만들어지지 않는다.
      Hibernate: 
          
          create table Movie (
             id bigint not null,
              name varchar(255),
              price integer not null,
              actor varchar(255),
              director 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 Album (
             id bigint not null,
              name varchar(255),
              price integer not null,
              artist varchar(255),
              primary key (id)
          )

 

결론!

  • 기본적으로 JOINED 전략을 가지고 가자라는 생각을 하고, 조인 테이블의 장단점과 단일 테이블 전략의 장단점을 고려하여 DBA와 상의하여 정하자!
  • 단, 구현 클래스마다 테이블 전략은 생각을 하지도 말자!
  • 정말 단순한 구조에 데이터도 얼마 안되고 확장성도 낮다 할 경우에는 단일 테이블 전략을 사용하는 것이 효율적이다.
  • 비즈니스 적으로 복잡해지고 중요한 테이블이다 싶으면, JOINED 전략을 사용하자!

그림참조