JPA - 상속관계매핑

2023. 12. 9. 05:06·Spring/JPA
728x90
반응형
SMALL

 

 

상속관계 매핑
  • 객체는 상속관계가 존재하지만, 관계형 DB에는 상속 관계가 없다.
  • 상속관계 매핑이라는 것은 객체의 상속 구조와 DB의 슈퍼타입 서브타입 관계를 매핑하는 것
슈퍼타입과 서브타입 논리 모델 → 물리모델로 구현하는 방법
  • 3가지 방법이 존재한다.
  • JPA가 이 3가지 방법과 매핑하려면 @Inheritence(strategy=InheritanceType.XXX)의 strategy를 설정해주면 된다.
  • XXX = ‘JOINED’,’SINGLE_TABLE’,’TABLE_PER_CLASS’로 구성되어 있다.
  • @DiscriminatorColumn(name=”DTYPE”) 부모 클래스에서 선언, 하위 클래스를 구분하는 용도의 컬럼을 생성 관례는 default=DTYPE
  • @DiscriminatorValue(”XXX”)하위 클래스에서 선언한다. 엔티티를 저장할 떄 슈퍼타입의 구분 컬럼에 저장할 값을 지정한다. 어노테이션을 선언하지 않을 경우 기본값으로 클래스 이름이 들어간다.
  •  
각 객체 코드 구현
//Item 
@Entity
@Inheritance(strategy = InheritanceType.XXX) // 상속 구현 전략 선택
public class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int price;
}

//Album
@Entity
public class Album extends Item {

    private String artist;
}

//Movie
@Entity
public class Movie extends Item {

    private String director;
    private String actor;
}

//Book
@Entity
public class Book extends Item {

    private String author;
    private String isbn;
}

 

조인 전략
  • 하위 테이블을 나누고 join으로 가져오는 구성 , 구분은 상위 테이블에서 DTYPE으로 구분한다.
  • 가장 정규화된 방식하이버네티의 조인 전략에서는 @DiscirminatorColumn을 선언하지 않으면 DTYPE컬럼이 생성되지 않는다.
  • 어차피 조인하면 앨범인지 무비인지는 알 수 있지만 그래도 DTYPE을 넣어주는 것이 명확하다.
  • @Entity @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn// 하위 테이블의 구분 컬럼 생성 기본값은 DTYPE public class Item {     @Id     @GeneratedValue(strategy = GenerationType.IDENTITY)     private Long id;     private String name;     private int price; }
실행된 DDL구문
  • 하위 테이블에서는 Item_id를 PK이면서 FK로 참조한다.
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)
    )
Hibernate:
    create table Item (
       DTYPE varchar(31) not null,
        id bigint generated by default as identity,
        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:
    alter table Album
       add constraint FKcve1ph6vw9ihye8rbk26h5jm9
       foreign key (id)
       references Item
Hibernate:
    alter table Book
       add constraint FKbwwc3a7ch631uyv1b5o9tvysi
       foreign key (id)
       references Item
Hibernate:
    alter table Movie
       add constraint FK5sq6d5agrc34ithpdfs0umo9g
       foreign key (id)
       references Item

 

이중 하나의 객체를 저장하면?

Book객체 생성

@Test
    @Transactional
    public void CheckInheritance(){
        Book book = new Book();
        book.setName("개미");
        book.setAuthor("베르나르베르베르");
        book.setPrice(10000);
        book.setIsbn(1234);
        em.persist(book);


    }

DDL 결과 구문

  • 쿼리가 두 개 나간다. 이 부분은 지연로딩을 해도 두개가 나간다.
  • DTYPE은 내가 설정한 ‘B’로 나가는걸 볼 수 있다.
*/ insert 
        into
            item
            (name, price, dtype, item_id) 
        values
            (?, ?, 'B', ?)

insert into item (name, price, dtype, item_id) values (?, ?, 'B', ?)
insert into item (name, price, dtype, item_id) values ('개미', 10000, 'B', 1);


insert 
        into
            book
            (author, isbn, item_id) 
        values
            (?, ?, ?)
  • 단일 테이블 : 모든 컬럼을 다 때려박는다. DTYPE으로 해당 테이블이 어떤 테이블인지 구분한다.
  • 성능때문에 고려하기도 한다.

 

하나의 객체를 조회하면?

Book객체 조회

@Test
    @Transactional
    public void CheckInheritance(){
        Book book = new Book();
        book.setName("개미");
        book.setAuthor("베르나르베르베르");
        book.setPrice(10000);
        book.setIsbn(1234);
        em.persist(book);

        em.flush();
        em.clear();

        Book result = em.find(Book.class, book.getId());
        


    }

DDL 구문

  • inner Join을 통해 조회하는 것을 확인할 수 있다.
select
        book0_.item_id as item_id2_3_0_,
        book0_1_.name as name3_3_0_,
        book0_1_.price as price4_3_0_,
        book0_.author as author1_1_0_,
        book0_.isbn as isbn2_1_0_ 
    from
        book book0_ 
    inner join
        item book0_1_ 
            on book0_.item_id=book0_1_.item_id 
    where
        book0_.item_id=?

 

 

 

 

 

 

 

 

단일 테이블 전략
  • 서비스 규모가 크지 않고, 굳이 조인 전략을 선택해서 복잡하게 갈 필요가 없다고 판단 될 때에는 한 테이블에 다 저장하고, DTYPE으로 구분하는 단일 테이블 전략을 선택할 수 있다.
  • INSERT 쿼리도 한 번, SELECT 쿼리도 한 번에 나간다.
  • Strategy를 SINGLE_TABLE로 적용하면 된다.
  • 한 테이블의 모든 컬럼을 저장하기 때문에, DTYPE없이는 테이블을 판단할 수 없다.

JPA의 장점

  • 테이블 구조의 변동이 일어났는데, 코드를 거의 손대지 않고 어노테이션만 수정했다.
  • 만약 JPA를 사용하지 않았더라면, 테이블의 구조의 변경이 일어나면 거의 모든 코드를 손대야 했다.

 

단일 테이블 전략 코드 구현

package study.jpa.queryDSL.domain.item;

import lombok.Data;

import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Data
@DiscriminatorColumn(name = "DTYPE")
public class Item {

    @Id @GeneratedValue
    @Column(name = "item_id")
    private  Long id;
    private String name;
    private int price;
}


//Test코드
@Test
    @Transactional
    public void CheckInheritance(){
        Book book = new Book();
        book.setName("개미");
        book.setAuthor("베르나르베르베르");
        book.setPrice(10000);
        book.setIsbn(1234);
        em.persist(book);

        em.flush();
        em.clear();

        Book result = em.find(Book.class, book.getId());



    }

DDL 구문

//insert
insert 
        into
            item
            (name, price, author, isbn, dtype, item_id) 
        values
            (?, ?, ?, ?, 'B', ?)
2023-12-09 04:37:01.029 TRACE 5240 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [개미]
2023-12-09 04:37:01.029 TRACE 5240 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [INTEGER] - [10000]
2023-12-09 04:37:01.029 TRACE 5240 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [베르나르베르베르]
2023-12-09 04:37:01.029 TRACE 5240 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [INTEGER] - [1234]
2023-12-09 04:37:01.029 TRACE 5240 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [5] as [BIGINT] - [1]


.//select
select
        book0_.item_id as item_id2_1_0_,
        book0_.name as name3_1_0_,
        book0_.price as price4_1_0_,
        book0_.author as author6_1_0_,
        book0_.isbn as isbn7_1_0_ 
    from
        item book0_ 
    where
        book0_.item_id=? 
        and book0_.dtype='B'

 

 

 

 

서브타입 테이블 전략
  • 조인 전략과 유사하지만, 슈퍼 타입의 컬럼들을 서브 타입으로 내린다.
  • 상위 테이블의 컬럼들이 중복되도록 허용하는 구조
  • 구현 클래스마다 테이블 생성 전략을 적용한다.

 

 

상속관계 매핑 정리
조인 전략
  • 장점
    • 테이블이 정규화되어 있다
    • 외래 키 참조 무결성 제약조건을 활용 가능하다
    • 다른 테이블들이 하나의 상위 테이블만 바라보도록 설계하는 것이 가능하다.
    • 저장 공간이 효율화된다. 정규화로 필요한 만큼만 소비된다.
  • 단점
    • 조회 시 조인을 많이 사용한다.
    • 단일 테이블 전략에 비하면 성능이 안나온다.
    • 쿼리가 복잡하다
    • 데이터 저장시 INSERT 쿼리가 사우이, 하위 테이블 두 번 발생한다.
  • 정리
    • 성능 저하라고 했지만 크게 영향이 있지는 않다.
    • 오히려 저장공간이 효율화 되기 때문에 장점이 크다.
    • 기본적으로는 조인 전략이 정석적

 

단일 테이블 전략
  • 장점
    • 조인이 필요 없으므로 성능이 빠르다
    • 조회 쿼리가 단순하다
  • 단점
    • 자식 엔티티가 매핑한 컬럼은 모두 NULL을 허용
    • 단일 테이블에 모든 것을 저장하므로 테이블이 커진다.
    • 상황에 따라 조인 전략과 교체하며 사용해야 한다.
클래스 별 테이블 전략
  • 잘 사용하지 않는다.
  • ORM을 사용하면 데이터와 객체 사이 TRADE OFF가 발생한다.
  • 장점
    • 서브 타입을 명확하게 구분해서 처리할 때 효과적
    • NOT NULL 제약조건이 간으하다
  • 단점
    • 여러 자식 테이블을 함께 조회할 떄 성능이 느리다(UNION ALL)
    • 자식 테이블을 통합해서 쿼리하기 어렵다
    • 만약 ITEM의 클래스에 값이 변경되면 모든 클래스에 영향을 미친다.

 

 

결론

  • 정석은 조인 전략
  • 조인 전략과 단일 전략의 성능을 고려하며 선택
  • 매우 단순하고 확장할 가능성이 적으면 단일 전략
  • 비즈니스 적으로 중요하고, 복잡하며, 확장될 확률이 높으면 조인 전략

 

클래스별 테이블 전략
조인 테이블 전략
단일 테이블 전략

 

 

728x90
반응형
SMALL

'Spring > JPA' 카테고리의 다른 글

@Where Deprecated되고 새로 쓰이는 @SQLRestriction  (1) 2024.11.20
JPA - DB 연결 예외 : 'url' attribute is not specified and no embedded datasource could be configured.  (0) 2024.06.14
JPA - 지연로딩과 즉시로딩  (1) 2023.12.10
JPA - Proxy  (2) 2023.12.10
JPA - MappedSuperClass  (0) 2023.12.10
'Spring/JPA' 카테고리의 다른 글
  • JPA - DB 연결 예외 : 'url' attribute is not specified and no embedded datasource could be configured.
  • JPA - 지연로딩과 즉시로딩
  • JPA - Proxy
  • JPA - MappedSuperClass
공부하고 기억하는 공간
공부하고 기억하는 공간
IT 비전공자로 시작하여 훌륭한 개발자가 되기 위해 공부하고 있는 공간입니다. 틀린 내용이나 부족한 부분이 있으면 댓글로 알려주세요 바로 수정하겠습니다.
    250x250
  • 공부하고 기억하는 공간
    IT - railroad
    공부하고 기억하는 공간
  • 전체
    오늘
    어제
    • 분류 전체보기 (315)
      • 면접 준비 (36)
        • OS (6)
        • Spring Security (0)
        • Java (2)
        • DB (9)
        • Network (3)
      • ElasticSearch (2)
      • Kafka (4)
      • Spring (22)
        • Spring Cloud (7)
        • Security6 (5)
        • JPA (12)
        • 프로젝트 리팩토링 회고록 (4)
        • Logging (8)
        • Batch (2)
      • Redis (17)
        • Redis 개념 (8)
        • Redis 채팅 (5)
        • Redis 읽기쓰기 전략 (1)
      • AWS (11)
      • 리눅스 (29)
        • 리눅스 마스터 2급 (5)
        • 네트워크(기초) (7)
        • 리눅스의 이해 (6)
        • 리눅스의 설치 (2)
        • 리눅스 운영 및 관리 (6)
      • JAVA-기초 (16)
        • JAVA기본 (11)
        • Design Pattern (5)
      • JSP (27)
        • JSP 기본 개념 (10)
        • JSP (1)
      • SQL (1)
      • TIL (36)
      • 문제 풀이 (2)
        • Programmers (9)
        • 백준 문제풀이 (28)
      • JavaScript (10)
      • HTML (17)
      • Ngrinder (1)
        • Ngrinder 문서 정리 (1)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      리눅스
      프로그래머스
      JavaScript
      redis
      java
      레디스
      자바 면접질문
      springsecurity
      redis 채팅
      Springframework
      백준
      JSP
      JS
      CSS
      spring redis
      Spring
      자바 면접
      리눅스마스터2급정리
      jsp request
      HTML
      jsp기초
      자바
      스프링프레임워크
      Til
      Spring Data Redis
      자바 반복문
      자바스크립트
      리눅스마스터2급
      자바 알고리즘
      자바기초
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.3
    공부하고 기억하는 공간
    JPA - 상속관계매핑
    상단으로

    티스토리툴바