Chapter7. 고급 매핑

김신영·2022년 11월 22일
0

JPA

목록 보기
5/14
post-thumbnail

상속 관계 매핑 (@Inheritance)

객체의 상속 구조와 데이터베이스의 슈퍼타입 서브타입 관계를 매핑하는 것

  • 객체지향
    - 상속 관계
  • 관계형 데이터베이스
    - Super-Type Sub-Type Relationship
    - @Inheritance(stragety = InheritanceType.SINGLE_TABLE)
    - @DiscriminatorColumn , @DiscriminatorValue

슈퍼타입 서브타입 모델

1. 각각의 테이블로 변환 (JOINED 전략)

부모, 자식 테이블 모두 엔티티로 만들고, 자식 테이블이 부모 테이블의 기본키를 받아서 기본키+외래키로 설정
대신 부모 테이블에 DTYPE 컬럼이 필요하다.

  • @Inheritance(strategy = InheritanceType.JOINED)
  • DTYPE 컬럼 필요
    - @DiscriminatorColumn
    - @DiscriminatorValue
  • 기본적으로 자식 테이블은 부모 테이블의 ID 컬럼명을 그래도 사용
  • 자식 테이블의 기본 키 컬럼명을 변경하고 싶으면, @PrimaryKeyJoinColumn(name = "BOOK_ID")
@Entity  
@Table(name = "ART_ITEM")  
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "ITEM_TYPE")  // default "DTYPE"
public abstract class ArtItem {  
    @Id @GeneratedValue  
    private Long id;  
    private String name;  
    private int price;  
}

@Entity  
@Table(name = "ALBUM")  
@DiscriminatorValue(value = "A")  
public class Album extends ArtItem {  
    private String artist;
}

@Entity  
@Table(name = "BOOK")  
@DiscriminatorValue(value = "B")  
public class Book extends ArtItem {  
    private String author;
}

@Entity  
@Table(name = "MOVIE")  
@DiscriminatorValue(value = "M")  
@PrimaryKeyJoinColumn(name = "BOOK_ID") // 기본 키 컬럼명 변경
public class Movie extends ArtItem {  
    private String director;  
    private String actor;
}    

장점

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

단점

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

2. 통합 테이블로 변환 (SINGLE_TABLE 전략)

  • 상속 관계 매핑의 기본 전략이다.
  • @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
  • 자식 엔티티가 매핑한 컬럼은 모두 null 허용해야 한다.
  • @DiscriminatorColumn 을 무조건 설정해야 한다.
  • @DiscriminatorValue를 지정하지 않으면, 기본적으로 엔티티 이름으로 사용한다.

장점

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

단점

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

3. 서브타입 테이블로 변환 (TABLE_PER_CLASS 전략)

  • @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
  • 자식 엔티티마다 테이블을 만든다.
  • @DiscriminatorColumn, @DiscriminatorValue 를 사용하지 않는다. (구분 칼럼을 사용하지 않는다.)
  • 일반적으로 추천하지 않는 전략

장점

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

단점

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

@MappedSuperclass

  • 테이블과 매핑하지 않고 자식 클래스에 엔티티의 매핑 정보를 상속하기 위해 사용한다.

  • @MappedSuperclass로 지정한 클래스는 엔티티가 아니므로, em.find() 나 JPQL에서 사용할 수 없다.

  • 이 클래스를 직접 생성해서 사용할 일은 거의 없으므로, 추상 클래스로 만드는것을 권장한다.

  • 부모로부터 물려받은 매핑 정보를 재정의하려면,
    - @AttributeOverrides
    - @AttributeOverride(name = "createdAt", column = @Column(name = "insertAt")

@MappedSuperclass
@EntityListeners(value = AuditingEntityListener.class)
@Getter
public abstract class BaseTimeEntity {
  @Column(updatable = false)
  @CreatedDate
  protected LocalDateTime createdAt;

  @LastModifiedDate
  protected LocalDateTime updatedAt;
}

@Entity
@Table(name = "MEMBER_INFO")
@AttributeOverrides({
	@AttributeOverride(name = "createdAt", column = @Column(name = "insertAt")),
	@AttributeOverride(name = "updatedAt", column = @Column(name = "updateAt"))
})
public class Member extends BaseTimeEntity {
	// ...
}

@Entity 는 @Entity이거나 @MappedSuperClass로 지정한 클래스만 상속받을 수 있다.

복합 키와 식별 관계 매핑

식별관계

  • 부모 테이블의 기본 키를 내려 받아서, 자식 테이블의 기본 키 + 외래 키로 사용하는 관계이다.

비식별 관계

  • 부모 테이블의 기본 키를 받아서, 자식 테이블의 외래 키로만 사용하는 관계이다.

필수적 비식별 관계 (Mandatory)

  • 외래 키에 NULL을 허용하지 않는다.

선택적 비식별 관계 (Optional)

  • 외래 키에 NULL을 허용한다.

복합 키

  • JPA에서 식별자(@Id)를 둘 이상 사용하려면 별도의 식별자 클래스를 만들어야 한다.

  • JPA는 영속성 컨텍스트에 엔티티를 보관할 때 엔티티의 식별자를 키로 사용한다.
    - 식별자를 구분하기 위해 equals, hashCode를 사용해서 동등성 비교를 한다.

  • JPA는 복합 키를 지원하기 위해 2가지 방법을 지원한다.
    - @IdClass
    - @EmbeddedId

  • 복합 키에는 @GeneratedValue 를 사용할 수 없다.

@IdClass

  • IdClass로 선언한 클래스 필수 조건
    1. Serializable 인터페이스 구현
    2. equals, hashCode 구현
    3. 기본 생성자
@Entity  
@Table(name = "PARENT")  
@IdClass(ParentId.class)  
public class Parent {  
    @Id  
    // @Column(name = "ID1")
    private Long id1;  // ParentId.id1 과 연결  
    @Id
    // @Column(name = "ID2")  
    private Long id2;  // ParentId.id2 와 연결
  
    private String name;
}

@EqualsAndHashCode  
public class ParentId implements Serializable {  

    private Long id1;  
    
    private Long id2;  
}
  • 데이터 저장시에 IdClass로 선언한 클래스를 직접 만들어서 세팅할 필요 없다.
  • JPA 내부에서 알아서 IdClass로 선언한 객체를 생성해서 식별자로 관리한다.
  • EntityManager를 통해 엔티티를 조회할 때, IdClass로 선언한 클래스를 활용해야한다.
ParentId parentId = ParentId.builder()  
    .id1(1L)  
    .id2(1L)  
    .build();  
  
Parent findParent = em.find(Parent.class, parentId);  // ParentId 클래스를 통해 조회

System.out.println("findParent = " + findParent);

복합 키를 외래 키로 매핑 설정

  • @JoinColumnname 속성과 referencedColumnName 속성 값이 같으면, referencedColumnName 은 생략해도 된다.
@Entity  
@Table(name = "CHILD")  
public class Child {  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;  
  
    @ManyToOne  
    @JoinColumns({  
        @JoinColumn(name = "PARENT_ID1", referencedColumnName = "ID1"),  
        @JoinColumn(name = "PARENT_ID2", referencedColumnName = "ID2"),  
    })  
    private Parent parent;  
  
    private String name;

@EmbeddedId

  • EmbeddedId를 설정한 클래스 필수 조건
    1. @Embeddable 어노테이션을 붙어주어야 한다.
    2. Serializable 인터페이스 구현
    3. equals, hashCode 구현
    4. 기본 생성자

  • EmbeddedId의 경우, 데이터를 저장할 때 EmbeddedId 클래스 객체를 직접 생성해야한다.

@Entity  
@Table(name = "PARENT")  
public class Parent {  
    @EmbeddedId  
    private ParentId2 id;  
  
    private String name;  
  
    @OneToMany(mappedBy = "parent")  
    private List<Child2> child2List;
}

@Embeddable  
@EqualsAndHashCode  
public class ParentId implements Serializable {  
//    @Column(name = "ID1")  
    private Long id1;  
//    @Column(name = "ID2")  
    private Long id2;  
}

@IdClass와 식별 관계

@Entity 
@Table(name = "PARENT")  
public class Parent {  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;  
  
    private String name;
}

@Entity
@Table(name = "CHILD")  
@IdClass(ChildId.class)  
public class Child {  
    @Id  
    private Long id;  
  
    @Id  
    @ManyToOne    
    @JoinColumn(name = "PARENT_ID")  
    private Parent parent;  
  
    private String name;
}

@EqualsAndHashCode
@NoArgsConstructor
public class ChildId implements Serializable {  
      
    private Long id;            // Child.id 매핑  
    
    private Long parent;        // Child.parent 매핑  
}

@Entity
@Table(name = "GRAND_CHILD")
@IdClass(GrandChildId.class)  
public class GrandChild {  
    @Id  
    private Long id;  
  
    @Id  
    @ManyToOne    
    @JoinColumns({  
        @JoinColumn(name = "CHILD_ID", referencedColumnName = "ID"),  
        @JoinColumn(name = "PARENT_ID", referencedColumnName = "PARENT_ID")  
    })  
    private Child child;  
  
    private String name;
}

@EqualsAndHashCode  
@NoArgsConstructor  
public class GrandChildId implements Serializable {  

    private Long id;        // GrandChild.id 매핑  
    
    private ChildId child;  // GrandChild.child 매핑  
}

@EmbeddedId 와 식별 관계

@MapsId

  • 외래키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻
  • @MapsId 의 속성 값은 @EmbeddedId 를 사용한 식별자 클래스의 기본 키 필드를 지정하면 된다.
@Entity 
@Table(name = "PARENT")  
@Getter  
@Setter  
@AllArgsConstructor  
@NoArgsConstructor  
@Builder  
public class Parent {  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;  
  
    private String name;
}

@Entity 
@Table(name = "CHILD")  
public class Child {  
    @EmbeddedId  
    private ChildId id;  
  
    @MapsId("parentId")  
    @ManyToOne  
    @JoinColumn(name = "PARENT_ID")  
    private Parent parent;  
  
    private String name;
}

@Embeddable  
@NoArgsConstructor  
public class ChildId implements Serializable {  
	
	// Child.parentId 로 매핑 -> @MapsId("parentId")로 매핑
    private Long parentId;

	// Child.id 로 매핑
    private Long id;
}

@Entity
@Table(name = "GRAND_CHILD")  
public class GrandChild {  
    @EmbeddedId  
    private GrandChildId id;  
  
    @MapsId("childId")  
    @ManyToOne  
    @JoinColumns({  
        @JoinColumn(name = "PARENT_ID", referencedColumnName = "PARENT_ID"),  
        @JoinColumn(name = "CHILD_ID", referencedColumnName = "ID"),  
    })  
    private Child child;  
  
    private String name;
}

@Embeddable  
@NoArgsConstructor  
public class GrandChildId implements Serializable {  

	// GrandChild.id 로 매핑
    private Long id;

	// GrandChild.childId 로 매핑 -> @MapsId("childId")로 매핑
    private ChildId childId;
}

비식별 관계로 구현

@Entity 
@Table(name = "PARENT")  
public class Parent {  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;  
  
    private String name;
}

@Entity
@Table(name = "CHILD")  
public class Child {  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;  
  
    @ManyToOne  
    @JoinColumn(name = "PARENT_ID")  
    private Parent parent;  
  
    private String name;
}

@Entity  
@Table(name = "GRAND_CHILD")  
public class GrandChild {  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;  
  
    @ManyToOne  
    @JoinColumn(name = "CHILD_ID")  
    private Child child;  
  
    private String name;
}

비식별 관계를 추천한다.

profile
Hello velog!

0개의 댓글