본문 바로가기
Backend/JPA

[JPA] 고급 매핑 (상속 관계 매핑, 공통 속성 매핑)

by 2245 2023. 12. 20.

출처

https://www.inflearn.com/course/ORM-JPA-Basic/dashboard

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 - 인프런

JPA 를 공부하고 책을 보며 어려웠던 내용을 위주로 먼저 보았습니다. 옆에서 1:1 과외해주는 것 같이 생생하고 이해 잘되는 설명, 예제(코드)가 너무 좋았습니다. 어느 것 하나 애매함없이 모두

www.inflearn.com

 

목차

     

     

     

    상속 관계 매핑

    • 관계형 데이터베이스는 상속 관계가 없습니다. (객체는 있습니다.)
    • 그나마, 슈퍼타입/서브타입 관계라는 모델링 기법이 객체 상속과 유사합니다. 
    • 따라서, 상속관계 매핑이란 객체의 상속 구조와 DB의 슈퍼타입/서브타입 관계를 매핑하는 것입니다.

    왼: 슈퍼 타입과 서브 타입으로 구성한 데이터베이스 논리 모델, 오: 객체의 상속 관계

     

    슈퍼 타입/서브타입 논리 모델을 실제 물리 모델로 구체화하는 3가지 방법

    • 모두 각각의 테이블로 변환 →  조인 전략 
    • 한 개의 통합 테이블로 변환 → 단일 테이블 전략
    • 서브타입 테이블로 변환 → 구현 클래스 테이블 전략
    참고
    객체 입장에서는 3개 중 어떤 방법을 사용하여 상속을 데이터베이스 테이블을 구성하던지 모두 같습니다.  

     

     

    객체 상속 관계 (기본 전략: 단일 테이블 전략)

    @Entity
    public class Item {
        @Id @GeneratedValue
        private Long id;
        private String name;
        private int price;
    }
    @Entity
    public class Album  extends Item {
        private String artist;
    }
    @Entity
    public class Movie extends Item {
        private String director;
        private String actor;
    }
    @Entity
    public class Book extends Item {
        private String author;
        private String isbn;
    }

     

    [실행 결과]

    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)
        )

     

     

     

    주요 어노테이션

    • @Inheritance(strategy = InheritanceType.xxx) 
      • JOINED: 조인 전략
      • SINGLE_TABLE: 단일 테이블 전략
      • TABLE_PER_CLASS: 구현 클래스마다 테이블 전략
    @Entity
    @Inheritance(strategy = InheritanceType.xxx)
    public class Item {
    	...
    }
    • @DiscriminatorColumn(name="DTYPE")
    • @DiscriminatorValue( “xxx” )

     

     

    조인 전략

    • DTYPE은 앨범인지, 영화인지, 책인지 구분하는 용도입니다.
    • 장점
      • 테이블 정규화
      • 외래 키 참조 무결성 제약 조건 활용이 가능합니다.
      • 저장 공간 효율화 (정규화가 되어 있으므로)
    • 단점
      • 조회 시 조인을 많이 사용하여 성능이 저하됩니다.
      • 조회 쿼리가 복잡합니다.
      • 데이터 저장 시 INSERT Query가 2번 호출됩니다.  
    참고 외래 키 참조 무결성 제약 조건
    이 제약 조건은 다른 테이블의 외래 키(Foreign key)가 참조하는 값이 항상 테이블의 기본 키(Primay key) 또는 고유한 키(Unique key)와 일치해야 함을 나타냅니다. 

    다음과 같은 중요한 목적을 가지고 있습니다. 
    - 데이터 일관성: 관련된 테이블 간의 데이터가 일치하고 무결하게 유지됩니다.
    - 참조 무결성: 외래 키가 참조하는 테이블의 행이 삭제되거나 수정될 떄, 참조된 테이블에서도 일관성을 유지하기 위해 제약이 적용됩니다. 

     

     

    [실습]

    @Entity
    @Inheritance(strategy = InheritanceType.JOINED)
    public class Item {
    	...
    }

     

    [실행 결과]

    Movie movie = new Movie();
    movie.setDirector("감독A");
    movie.setActor("배우A");
    movie.setName("바람과함께사라지다");
    movie.setPrice(10000);
    
    em.persist(movie);
    Hibernate: 
      /* insert hellojpa.Movie
          */ insert 
          into
              Item
              (name, price, id) 
          values
              (?, ?, ?)
    Hibernate: 
      /* insert hellojpa.Movie
          */ insert 
          into
              Movie
              (actor, director, id) 
          values
              (?, ?, ?)

    • 참고: ID 값은 동일합니다. ID가 PCK이면서 FK이기 떄문입니다. 

     

    Movie findMovie = em.find(Movie.class, movie.getId());

     

    Hibernate:
      select 
        id as id1,
        name as name2,
        price as price3,
        actor as actor1,
        director as director2
      from
        Movie
      inner join 
        Item
      where id = ?

     

     

    @DiscriminatorColumn(name=“DTYPE”)

    • 자식 타입의 어떤 타입이 들어갔는지 DB에서 확인하는 용도입니다. 
    • default는 “DTYPE”이고, 원하는 이름으로 변경 가능합니다.
    @Entity
    @Inheritance(strategy = InheritanceType.JOINED)
    @DiscriminatorColumn
    public class Item {
    	...
    }

     

    @DiscriminatorValue(“XXX”)

    • DTYPE에 엔티티명이 아닌 지정한 값을 저장합니다. (default는 엔티티명 - Movie)
    @Entity
    @DiscriminatorValue("M")
    public class Movie extends Item {

     

     

     


    단일 테이블 전략 (기본 전략)

    • 장점
      • 조인이 필요 없으므로 일반적으로 조회 성능이 빠릅니다.
      • 조회 쿼리가 단순합니다.
    • 단점
      • 자식 엔티티가 매핑한 컬럼은 모두 null이 허용됩니다. (NAME과 PRICE 빼고 모두 null을 허용)
      • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있습니다. 상황에 따라서 조회 성능이 오히려 느려질 수 있습니다.
    • @DiscriminatorColumn을 생략해도 DTYPE이 자동으로 추가가 됩니다.

     

    참고 
    JOINED 전략에서 SINGLE_TABLE 전략으로 전환하는 과정에서 변경한 곳은 @Inheritance 코드 뿐입니다. 
    Movie 객체를 생성하고, em.persist 하거나 find 하는 코드는 수정한 곳이 없습니다. 
    즉, JPA 덕분에 JOINED를 사용하다가 성능이 너무 안나와서 SINGLE_TABLE 전략으로 전환해도 다른 코드를 손댈 필요가 없습니다. 

     

     

     


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

    @Entity
    @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
    public abstract class Item {   //추상클래스로 생성해야 한다.
    • 이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천 X
    • 장점
      • 서브 타입을 명확하게 구분해서 쿼리를 날릴 때 효과적입니다. (단일 테이블만 사용하므로)
      • not null 제약조건 사용이 가능합니다. 
    • 단점
      • 여러 자식 테이블을 함께 조회할 때 성능이 느립니다. (다수의 UNION SQL 필요)
      • 자식 테이블을 통합해서 쿼리하기 어렵습니다. 
      • Item에 새로운 타입이 추가가 되거나 변경이 필요할 때, 테이블을 다 뜯어 고쳐야 합니다.
    Item item = em.find(Item.class, movie.getId());
    select
            item0_.id as id1_2_0_,
            item0_.name as name2_2_0_,
            item0_.price as price3_2_0_,
            item0_.artist as artist1_0_0_,
            item0_.author as author1_1_0_,
            item0_.isbn as isbn2_1_0_,
            item0_.actor as actor1_4_0_,
            item0_.director as director2_4_0_,
            item0_.clazz_ as clazz_0_ 
        from
            ( select
                id,
                name,
                price,
                artist,
                null as author,
                null as isbn,
                null as actor,
                null as director,
                1 as clazz_ 
            from
                Album 
            union
            all select
                id,
                name,
                price,
                null as artist,
                author,
                isbn,
                null as actor,
                null as director,
                2 as clazz_ 
            from
                Book 
            union
            all select
                id,
                name,
                price,
                null as artist,
                null as author,
                null as isbn,
                actor,
                director,
                3 as clazz_ 
            from
                Movie 
        ) item0_ 
    where
        item0_.id=?

     

     

    결론

    • 조인 전략과 단일 테이블 전략 중 장단점을 고려해서 선택하자
    • 구현 클래스마다 테이블 전략은 선택지에 두지 말것
    • 기본으로는 조인 전략을 선택하는데, 구조가 매우 단순하고, 데이터 양도 얼마 되지 않고 확장 가능성도 별로 없는 것 같다면 단일 테이블을 선택하는 것이 좋다.
    • 하지만, 비지니스 적으로 중요하고 복잡하다면, 조인 전략을 선택하는 것이 좋다.

     

     


    공통 속성 매핑 : @MappedSuperclass

    공통 매핑 정보가 필요할 때 사용합니다. (ex) id, name)

    • 객체 입장에서 반복되는 공통 속성이 존재할 경우, 상속을 통해 해결할 수 있습니다.
    • 다만, 상속 관계를 DB에서 매핑하는 것이 아니라 단순히 테이블 각각의 컬럼으로 추가할 때 사용합니다. 
    • 상속관계 매핑 X
    • 엔티티 X, 테이블 매핑 X
    • 부모 클래스를 상속 받는 자식 클래스에게 공통 속성만 제공하는 역할을 합니다. 
    • 조회, 검색 불가능합니다. (em.find(BaseEntity) 불가)
    • 직접 생성해서 사용할 일이 없으므로 추상 클래스로 권장합니다. 
    • 테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑정보를 모으는 역할입니다. 
    • 주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용합니다. 
    • 참고: 엔티티는 같은 @Entity나 @MappedSuperclass로 지정한 클래스만 상속이 가능합니다.

     

    @MappedSuperclass
    public abstract class BaseEntity {
    
        private String createdBy;
        private LocalDateTime createdDate;
        private String lastModifiedBy;
        private LocalDateTime lastModifiedDate;
    
        //Getter, Setter ...
    }
    @Entity
    public class Member extends BaseEntity {

     

     

     


    실전 예제 - 4. 상속관계 매핑, 공통 속성 매핑

    요구사항

    • 상품의 종류는 음반, 도서, 영화가 있고 이후 더 확장될 수 있습니다.
    • 모든 데이터는 등록일과 수정일이 필수입니다.

     

    도메인 모델

     

     

     

     

     

     

    [상속 관계]

    @Entity
    @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    @DiscriminatorColumn
    public abstract class Item {
    @Entity
    public class Album extends Item {
    
        private String artist;
        private String etc;
    
        //Getter, Setter ...
    }
    @Entity
    public class Book extends Item {
    
        private String author;
        private String isbn;
    
        //Getter, Setter ...
    }
    @Entity
    public class Movie extends Item {
    
        private String director;
        private String actor;
    
    		//Getter, Setter ...
    }

     

     

    [공통 속성]

    @MappedSuperclass
    public class BaseEntity {
        private String createdBy;
        private LocalDateTime createdDate;
        private String lastModifiedBy;
        private LocalDateTime lastModifiedDate;
    
        //Getter, Setter ...
    }
    @Entity
    public class Member extends BaseEntity {
    • Item테이블에 extends BaseEntity를 넣고, 자식 클래스인 Album, Book, Movie를 제외한 모든 클래스에 extends BaseEntity를 삽입합니다. 

     

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

    [JPA] 값 타입  (0) 2023.12.20
    [JPA] 프록시와 연관관계 관리  (0) 2023.12.20
    [JPA] 다양한 연관관계 매핑  (0) 2023.12.18
    [JPA] 연관관계 매핑  (0) 2023.12.18
    [JPA] 엔티티 매핑  (0) 2023.12.18