JPA

7장 고급 매핑

iksadnorth 2023. 8. 31. 20:47

해당 게시물은 책 '자바 ORM 표준 프로그래밍'을 읽고 작성했습니다.

👣 개요

해당 게시물은 OOP와 SQL에서의 간극을 메꾸는 여러 기술을 나열하는 방식으로 서술된다.
아직 OOP와 SQL 사이에 간극이 존재하는데 내용은 다음과 같다.

1. OOP의 상속관계

2. SQL에서의 복합 키 및 식별 관계

 

👣 상속 관계 매핑

SQL에는 상속 개념이 존재하지 않는다.
대신 슈퍼타입 서브타입 관계라는 개념이 존재한다.
따라서 OOP의 상속은 SQL에서의 슈퍼타입 서브타입 관계로 매핑한다.

위 방법은 3가지의 전략으로 적용할 수 있다.

1. Joined Strategy
2. Single-Table Strategy
3. Table-per-Concrete-Class Strategy

Joined Strategy

Joined Strategy는 부모 테이블[이하 P]을 만들고 해당 테이블에 DTYPE과 같은
자식 테이블[이하 C]을 구분할 만한 칼럼을 추가합니다.
그리고 각 C은 P의 PK를 (FK + PK)로 사용하는 테이블을 만듭니다.

이를 통해 새로운 데이터를 저장할 때마다 P과 C에 분리해서 저장하고
조회할 때는 P과 C를 Join해서 조회합니다.

Single-Table  Strategy

Single-Table Strategy는 아예 1개의 테이블에서 모든 자식 테이블을 뭉쳐 보관하는 방법이다.
이 방법은 조회속도가 무척 빠르지만 공간 낭비가 심하고 Null 제약 조건을 사용할 수 없다는 단점이 있다.

Table-per-Concrete-Class Strategy

Table-per-Concrete-Class Strategy는 아예 각자도생하는 방법이라고 볼 수 있다.
해당 방식은 유일하게 DTYPE 같은 칼럼을 사용하지 않아도 되지만
성능이 느리고 통합 쿼리가 어려워서 사용하지 않는 것이 좋다. 

위 3가지의 방법은 아래와 같은 방식으로 매핑해서 JPA가 인식할 수 있도록 한다.

@Entity
@Ingeritance(strategy = Ingeritance.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public abstract class BaseEntity {
    @Id
    private Long id;
    private LocalDateTime createdAt;
}

@Entity
@DiscriminatorValue("MEM")
public class Member extends BaseEntity {
    private String username;
    private String email;
}

@Entity
@DiscriminatorValue("PRO")
public class Product extends BaseEntity {
    private String name;
    private Long price;
}

여기서
@DiscriminatorColumn(name = "DTYPE")은 자식 테이블을 구분하는 칼럼명을 DTYPE으로 설정하겠다는 소리고
@DiscriminatorValue("MEM")는 DTYPE을 MEM으로 설정하겠다는 소리다.

 

👣 @MappedSuperclass

위와 같이 SQL에서도 상속과 같이 부모 테이블에서의 변화가 자식 테이블에도 전파가 되길 바라면
슈퍼타입 서브타입 관계를 사용하면 된다.

하지만 단순하게 엔티티 사이의 공통 칼럼을 관리하고 싶다는 목적을 가지고 있다면
굳이 부모 테이블의 변화가 전파되지 않아도 된다. 오히려 부모 테이블의 변화가 
자식 테이블로 전파되는 것을 지양할 수도 있다.

이런 경우 사용하는 어노테이션이 @MappedSuperclass다.
이 어노테이션을 이용하면 공통된 칼럼을 정의하는 엔티티의 중복 코드를 줄일 수 있다.

@MappedSuperclass 적용 전

@Entity
public class Member {
    @Id
    private Long id;
    private LocalDateTime createdAt;

    private String username;
    private String email;
}

@Entity
public class Product {
    @Id
    private Long id;
    private LocalDateTime createdAt;
    
    private String name;
    private Long price;
}

@MappedSuperclass 적용 후

@MappedSuperclass
public abstract class BaseEntity {
    @Id
    private Long id;
    private LocalDateTime createdAt;
}

@Entity
public class Member extends BaseEntity {
    private String username;
    private String email;
}

@Entity
public class Product extends BaseEntity {
    private String name;
    private Long price;
}

@MappedSuperclass 적용을 적용하나 적용하지 않거나 모두 SQL 테이블 구조는 동일하다.
단순히 중복 코드를 없애기 위해 사용한다.

@MappedSuperclass가 적용된 클래스는 엔티티로서 의미가 없기에 abstract을 적용해
객체 생성을 막는 것이 좋다.

놀랍게도 상속받은 엔티티의 필드를 재정의해서 사용할 수도 있다.

@Entity
@AttributeOverride(name = "id, column = @Column(name = "mem_id"))
public class Member extens BaseEntity {
    ...
}

@Entity
@AssociationOverride(name = "child", joinColumns = @JoinColumn(name = "new_child_id"))
public class Member extens BaseEntity {
    ...
}

@AttributeOverride는 단순히 Column을 재정의하기 위해 사용하는 Annotation이고
@AssociationOverride는 연관관계를 재정의하기 위해 사용하는 Annotation이다.

 

👣 복합 키와 식별 관계 매핑

DB를 구성할 때, PK가 복합 키, 즉 여러 칼럼을 이용한 키로 설정될 수도 있다.
이러한 경우 단순히 @Id를 n개의 칼럼에 적용하면 오류를 발생시킨다.
오류를 발생시키지 않으려면 복합 키 클래스를 만들어주고 매핑해야 한다.

그에 대한 방법이 2가지가 존재한다.

1. @IdClass 사용
2. @EmbeddedId 사용

@Entity
@IdClass(CompositeKey.class)
public class Member {
    @Id
    private String username;
    @Id
    private String email;
    
    ...
}

@EqualsAndHashCode
@NoArgsConstructor
public class CompositeKey implements Serializable {
    private String username;
    private String email;
    
    public CompositeKey(String eusername, String email) { ... }
}

@IdClass
@IdClass에 복합 키로 사용할 클래스를 넣어주면 된다.
문제는 복합 키 클래스가 갖춰야 할 조건이 많다는 것이다.

1. 복합 키 클래스의 필드명과 엔티티 클래스의 필드명이 일치해야 한다.
2. Serializable 인터페이스를 구현해야 한다.
3. equals, hashcode를 구현해야 한다.
4. 기본생성자가 있어야 한다.
5. 복합 키 클래스가 public이어야 한다.

 

@Entity
public class Member {
    @EmbeddedId
    private CompositeKey id;
    
    ...
}

@Embeddable
@EqualsAndHashCode
@NoArgsConstructor
public class CompositeKey implements Serializable {
    private String username;
    private String email;
    
    public CompositeKey(String eusername, String email) { ... }
}

@EmbeddedId
복합 키 클래스를 아예 엔티티의 필드로 지정하고 해당 필드에 @EmbeddedId를 적용하면 된다.
마찬가지로 복합 키 클래스는 특정 조건을 만족해야 한다.
그 특정 조건은 @IdClass와 동일하다

 

 

'JPA' 카테고리의 다른 글

9장 값 타입  (0) 2023.09.03
8장 프록시와 연관관계 관리  (0) 2023.08.31
6장 다양한 연관관계 매핑  (0) 2023.08.31
5장 연관관계 매핑 기초  (0) 2023.08.30
4장 엔티티 매핑  (0) 2023.08.30