[JPA] 고급 매핑

yookyungmin·2023년 8월 30일
0

상속 관계 매핑

  • 관계형 데이터 베이스에는 객체지향 언어에서 다루는 상속이라는 개념이 없다.
  • 슈퍼타입 서브타입 관계라는 모델링 기법이 상속 개념과 가장 유사하다.


슈퍼타입 서브타입 논리 모델의 테이블 구현 방법 3가지

  • 각각의 테이블로 변환 (조인 전략)
  • 통합 테이블로 변환 (단일 테이블 전략)
  • 서브타입 테이블로 변환 (테이블 전략)

조인 전략

  • 엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본키를 받아서 기본 키 + 외래 키로 사용하는 전략
  • 조회할 때 조인을 자주 사용

주의

  • 객체는 타입으로 구분할 수 있지만 테이블은 타입의 개념이 없다.
  • 구분하는 컬럼을 추가해야 한다 -> DTYPE 컬럼
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscrimnatorColumn(name = "DTYPE")
public abstract class Item{
	@Id @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;
    
    private String name; //이름
    private int price; //가격
    
    }
    @Entity
    @DiscriminatorValue("A")
    public class Album extends Item{
    	private String artist;
    }
    
    @Entity
    @DiscriminatorValue("M")
    public class Movie extends Item{
    	private String director; //감독
        private String actor; //배우
    }
    1. @lnheritance(strategy = InheritanceType.JOINED): 상속 매핑은 부모 클래스에 @lnheritance 를 사용해야 한다. 그리고 매핑 전략을 지정해야 하는데 여기서는 조인 전략을 사용하므로 InheritanceType.JOINED를 사용했다.
    1. @DiscriminatorColumn(name = "DTYPE"): 부모 클래스에 구분 컬럼을 지정한다. 이 컬럼으로
      저장된 자식 테이블을 구분할 수 있다. 기본값이 D7YPE이므로 @DiscriminatorColumn으로 줄여사용 가능
    1. DiscriminatorValue("M"): 엔티티를 저장할 때 구분 컬럼에 입력할 값을 지정한다. 만약 영화 엔티티를 저장하면 구분 컬럼인 DTYPE에 값 M이 저장된다.

기본값으로 자식 테이블은 부모 테이블의 ID컬럼명을 그대로 사용하는데, 만약 자식 테이블의 기본키 컬럼명을 변경하고 싶으면 @PrimaryKeyJoinColumn을 사용하면 된다.

@Entity
@DiscriminatorValue("B")
@PrimaryKeyJoinColumn(name = "BOOK_ID") //ID 재정의
public class Book extends Item{
	private String author; //작가
    private Strin isbn; //ISBN
}

Join 전략 장점

  • 테이블이 정규화 된다.
  • 외래키 참조 무결성 제약조건을 활용할 수 있다.
  • 저장공간을 효율적으로 사용한다.

Join 전략 단점

  • 조회할 때 조인이 많이 사용되므로 성능이 저하될 수 있다.
  • 조회 쿼리가 복잡하다.
  • 데이터를 등록할 INSERT SQL을 두 번 실행한다.

특징

  • JPA 표준 명세는 구분 컬럼을 사용
  • 하이버네이트를 포함한 몇몇 구현체는 구분 컬럼(@DiscriminatorColumn) 없이도 동작한다.

관련 어노테이션

  • @PrimaryKeyJoinColumn, @DiscriminatorColumn, @DiscriminatorValue

단일 테이블 전략

  • 테이블을 하나만 사용하는 전략
  • 구분 컬럼(DTYPE)으로 어떤 자식 데이터가 저장되었는지 구분한다.
  • 조회할 때 조인을 사용하지 않으므로 일반적으로 가장 빠르다.

주의

  • 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.

    Book 엔티티를 저장하면 ITEM 테이블의 AUTHOR, ISBN 컬럼만 사용하고 다른 엔티티와 매핑된 ARTIST, DIRECTOR, ACTOR 컬럼은 사용하지 않으므로 null이 입력되기 때문이다

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE”)
public abstract class Item {
@Id SGeneratedValue
@Column(name = DTYPE)
private Long id;

private String name; //0|름
private int price; //가격
}
@Entity
@DiscriminatorValue("A")
public class Album extends Item { ... }

@Entity
@DiscriminatorValue("M")
public class Movie extends Item { ... }

@Entity
@DiscriminatorValue(nBn)
public class Book extends Item { ... }
  • InheritanceType.SINGLE_TABLE로 지정하면 단일 테이블 전략을 사용한다.
  • 테이블 하나에 모든 것을 통합하므로 구분 컬럼을 필수로 사용해야 한다.
  • 단일 테이블 전략의 장단점은 하나의 테이블을 사용하는 특징과 관련 있다.

단일 테이블 전략 장점

  • 조인이 필요 없으므로 일반적으로 조회 성능이 빠르다.
  • 조회 쿼리가 단순하다.

단일 테이블 전략 단점

  • 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.
  • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 그러므로 상황에 따라서는 조회 성능이 오히려 느려질 수 있다.

특징

  • 구분 컬럼을 꼭 사용해야 한다. @DiscrimnatorColumn
  • @DiscrimnatorColumn을 지정하지 않으면 기본으로 엔티티 이름을 사용한다. (예 Movie, Album, Book)

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

  • 자식 엔티티마다 테이블을 만든다.
  • 자식 테이블에 각각에 필요한 컬럼이 모두 있다.
  • InheritanceType.TABLE_PER_CLASS를 선택하면 구현 클래스마다 테이블 전략을 사용한다.
  • 일반적으로 추천하지 않는 전략이다.

장점

  • 서브 타입을 구분해서 처리할 때 효과적이다.
  • not null 제약조건을 사용할 수 있다.

단점

  • 여러 자식 테이블을 함께 조회할 때 성능이 느리다.(SQL 에 UNION을 사용해야 한다.)
  • 자식 테이블을 통합해서 쿼리하기 어렵다.

특징

  • 구분 컬럼을 사용하지 않는다

이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천하지 않는 전략이다. 조인이나 단일 테이블 전략을 고려하자.

@MappedSuperclass

  • 부모 클래스는 테이블과 매핑하지 않고 자식 클래스에게 매핑 정보만 제공하고 싶으면 사용한다.
  • 비유를 하자면 추상클래스와 비슷.
  • @Entitiy는 실제 테이블과 매핑 되지만 @MappedSuperClass는 실제 테이블과 매핑되지 않는다.
  • 단순히 매핑 정보를 상속할 목적으로만 사용된다.

회원과 판매자는 서로 관계가 없는 테이블과 엔티티다.
테이블은 그대로 두고 객체 모델의 id, name 두 공통 속성을 부모 클래스로 모으고 객체 상속 관계로 만들어보자.

@MappedSuperclass
public abstract class BaseEntity{
	@Id @GeneratedValue
    private Long id;
    private String name;
}

@Entity
public class Member extends BaseEntity{
	//ID 상속
    //NAME상속
    private String email;
}

@Entity
public class Seller extends BaseENtity{
	//ID 상속
    //NAME 상속
    private String shopName;
}
  • BaseEntity에는 객체들이 주로 사용하는 공통 매핑 정보를 정의 했다.
  • 자식 엔티티들은 상속을 통해 BaseEntity의 매핑 정보를 물려 받았다.
  • BaseEntity는 테이블과 매핑할 필요가 없고
    자식 엔티티에게 공통으로 사용되는 매핑 정보만 제공하면 된다. 따라서 @MappedSuperclass를 사용했다.

부모로 물려받은 매핑 정보 재정의

  • @AttributeOverride, @AttributeOverrides

부모로 물려받은 연관관계 재정의

  • @AssociationOverrides, @AssociationOverride
@Entity
@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID"))
public class Member extends BaseEntity{...}
  • 부모에게 상속받은 id 속성의 컬럼명을 MEMBER_ID로 재정의 했다.
  • 두 개 이상 재정의 @AttributeOverrides
@Entity
@AttributeOverrides({ @AttributeOverrides(name = "id", column = @Column(name = "MEMBER_ID")),
@AttributeOverrides(name = "name", column = @Column(name = "MEMBER_NAME"))})
public class Member extneds BaseEntity {...}

@MappedSuperclass 특징 정리

  • 테이블과 매핑되지 않고 자식 클래스에 엔티티의 매핑 정보를 상속하기 위해 사용
  • 지정한 클래스는 엔티티가 아니므로 em.find() 나 JPQL 에서 사용할 수 없다.
  • 이 클래스를 직접 생성해서 사용할 일은 거의 없으므로 추상 클래스로 만드는것을 권장한다.
  • 테이블과는 관계가 없고, 엔티티가 공통으로 사용하는 매핑 정보를 모아주는 역할
  • ORM 에서 진정한 상속 매핑은 이전에 학습한 객체 상속을 데이터베이스의 슈퍼타입 서브타입 관계와 매핑하는 것이다.
  • 등록일자, 수정일자, 등록자, 수정같은 여러 엔티티에서 공통으로 사용하는 속성을 효과적으로 관리 할 수 있다.

    엔티티(@Entity) 는 엔티티이거나 @MappedSuperclass로 지정한 클래스만 상속 받을 수 있다.

0개의 댓글