카테고리 없음

자바 ORM 표준 JPA 프로그래밍 - 기본편 섹션 7. 고급 매핑

정현3 2022. 5. 29. 13:35

< 1. 상속관계 매핑 >

ORM에서 이야기하는 '상속 관계 매핑'은 객체의  '상속 구조'와. 데이터베이스의 '슈퍼타입, 서브타입' 관계를 매핑하는 것이다.

관계형 데이터베이스는 상속관계가 없다

- '슈퍼타입 서브타입 관계'라는 '모델링 기법'이 '객체 상속'과 유사

- 상속 관계 매핑 : 객체의 상속과 구조와 DB의 슈퍼타입, 서브타입 관계를 매핑

관계형 DB설계는 '논리모델'과 '물리모델'이 있다

'논리모델'은 공통적인 속성은 물품에 넣는다 - 슈퍼타입 서브타입

객체는 명확하게 상속관계가 있다. 

 

상속관계 매핑

- 슈퍼타입 서브타입 논리모델을 '실제 물리 모델'로 구현하는 방법

1. 각각 테이블로 변환 -> JOIN 전략

2. 통합 테이블로 변환 -> 단일 테이블 전략

3. 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략

-> 타입을 구분하는 '컬럼'을 추가해야 한다. 여기서는 DTYPE 컬럼을 '구분 컬럼'으로 사용

1. 조인 전략 

-> Item이라는 테이블을 만들고 ALBUM, MOVIE,BOOK 테이블을 만든후 데이터를 나누고 JOIN 으로 구성하는것

-> INSERT는 두번을 하고 조회는 PK로 JOIN을 해서 가지고 온다. '구분하는 컬럼'을 따로 둔다

-> 데이터를 가장 정규화된 방식으로 JOIN. DB설계를 할 수 있다

 

장점 

- '테이블 정규화'

- 외래 키 참조 '무결성 제약조건' 활용가능 -> ITEM_ID 를 사용가능

- 저장공간 효율화

단점

- 조회시 JOIN을 많이 사용, 성능 저하

- 조회 커리가 복잡함

- 데이터 저장시 INSERT SQL 2번 호출

--> JPA 표준 명세는 '구분 컬럼'을 사용하도록 하지만 '하이버네이트'를 포함한 몇몇 구분체는 '구분 컬럼(@DiscriminatorColumn)없이도 동작한다'

package hellojpa;

import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.JOINED) // 조인전략의 테이블 설계 - 상속매핑은 부모 클래스에 선언해야 한다
@DiscriminatorColumn //DTYPE가 생긴다, DB에서 작업활때 어디에서 왔는지 확인하기 위해 필요하다
public abstract class Item {

    @Id @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;

    private String name;
    private int price;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }


}​
package hellojpa;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue("A") //엔티티를 저장할 때 '구분 컬럼'에 입력할 값을 저장한다
public class Album extends Item{

    private String artist;
}
package hellojpa;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue("B")
public class Book extends Item{

    private String author;
    private String isbn;
}
package hellojpa;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue("M")
@PrimaryKeyJoinColumn(name = "MOVIE_ID") //자식테이블의 '기본 키 컬럼명'을 변경(기본 값은 부모 테이블의 ID 컬럼명)
public class Movie extends Item{

    private String director;

    private String actor;


    public String getDirector() {
        return director;
    }

    public void setDirector(String director) {
        this.director = director;
    }

    public String getActor() {
        return actor;
    }

    public void setActor(String actor) {
        this.actor = actor;
    }


}

    try {

        Movie movie = new Movie();
        movie.setDirector("aaaa");
        movie.setActor("bbbb");
        movie.setName("바람과 함께 사라지다");
        movie.setPrice(10000);

        em.persist(movie);

        em.flush(); // 영속성 컨텍스트에 있는것을 DB에 날리고
        em.clear(); // 영속성 컨텍스트를 깔끔하게 제거한다 - 1차캐시에 아무것도 남지 않는다.

        Movie findMove = em.find(Movie.class, movie.getId()); //조회
        System.out.println("findMove = " + findMove);
        
        tx.commit();

    } catch (Exception e) {
        tx.rollback();
    } finally {
        em.close();
    }
    emf.close();
    }
}

2. 단일 테이블 전략

-> '논리모델'을 한 테이블로 합쳐버린다. 컬럼을 한 테이블에 다 때려박아버린다. DTYPE로 구분한다.

-> 성능이 가장 잘나온다.

-> INSERT도 잘나오고, 쿼리도 확인할 필요없다.

--> 주의점은 '자식 엔티티'가 매핑한 컬럼은 모두 null을 '허용'해야 한다.

 

package hellojpa;

import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) 
@DiscriminatorColumn //DTYPE가 생긴다, DB에서 작업활때 어디에서 왔는지 확인하기 위해 필요하다
public abstract class Item {

장점 

- JOIN이 필요없으므로 일반적으로 '조회 성능'이 빠름

- 조회 커리가 단순함

단점

- '자식 엔티티'가 매핑한 컬럼은 모두 null 허용 - name과 price빼고는 모두 허용해야 한다.

- '단일 테이블'에 모든 것을 저장하므로 테이블이 커질 수 있다. 상황에 따라서 '조회 성능'이 오히려 느려질 수 있다

-> ALBUM, MOVIE, BOOK 테이블만 만든후 각각 정보를 가지고 있는다.

package hellojpa;

import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@DiscriminatorColumn
public abstract class Item {

-> "쓰면 안되는 전략"

 

< 2. @MappedSuperclass >

부모 클래스는 '테이블'과 매핑하지 않고 부모클래스를 상속받는 '자식 클래스'에게 '매핑 정보'만 제공하고 싶으면 @MappedSuperclass를 사용하면 된다. @MappedSuperclass는 '실제 테이블'과는 매핑되지 않는다. 이것은 단순히 매핑정보를 '상속'할 목적으로만 사용된다.

-> 객체입장에서 속성만 따로 사용하고 싶을 때 '공통 매핑 정보'가 필요할때 쓰는것

-> '상속관계 매핑'이 아니다

-> '엔티티'가 아니다, '테이블'과 매핑하지 않는다.

-> 부모 클래스를 상속받는 '자식클래스'에 '매핑정보'만 제공한다

-> 조회, 검색 불가(em.find(BaseEntity) 불가)

-> 직접 생성해서 사용할 일이 없으므로 '추상 클래스' 권장

@MappedSuperclass
public abstract class BaseEntity {

    @Column(name = "INSERT_MEMBER")
    private String createdBy;
    private LocalDateTime createDate;
    @Column(name = "UPDATE_MEMBER")
    private String lastModifiedBy;
    private LocalDateTime lastModifiedDate;
@Entity
public class Member extends BaseEntity{
@Entity
public class Team extends BaseEntity{

-> '테이블'과 관계가 없고, 단순히 '엔티티'가 공통으로 사용하는 '매핑 정보'모으는 역할이다

-> 주로 등록일, 수정일, 등록자, 수정자 같은 '전체 엔티티'에서 '공통으로 적용하는 정보'를 모을때 사용한다

참고 : @Entity 클래스는 엔티티나 @MappedSuperclass로 지정한 클래스만 '상속'가능

-> 부모로부터 물려받은 '매핑 정보'를 재정의 하려면 @AttributeOverrides나 @AttributeOverride를 사용하고,

-> 연관관계를 재정의 하려면 @AssociationOverrides나 @AssociationOverride를 사용한다.