beadss / jpa-study

jpa슽터디입니다
1 stars 2 forks source link

7장 정리 완료(mjy) #20

Open beadss opened 5 years ago

beadss commented 5 years ago

고급 매핑

상속관계 매핑

객체의 상속관계를 데이터베이스와 매핑하는 방법 image DB에서는 슈퍼타입 서브타입 논리 모델이 객체지향의 상속과 가장 유사하며, 이를 물리 모델로 구현한 것들을 객체의 상속관계와 매핑하는 방법을 소개한다.

중점은 객체의 상속 관계이며, 별다른 DB side 손해 없이 추상화 된 형태로 핸들링 할 수 있는 것을 목표로 하는 기능이다.

데이터 중심으로 생각하면, 왜 굳이 이런 기능이 존재하는지 하는지, 잘 이해가 되지 않을 수 있다.

조인 전략

슈퍼타입 테이블, 서브타입 테이블(들)을 각각의 물리 테이블로 생성하는 방식이다. 서브타입은 슈퍼타입과 1대1 식별관계이다. 어떤 서브타입과 JOIN할지를 구분하기 위해서, 슈퍼타입에 Type 컬럼을 필요로 한다.

@Entity
public class Bag {
    @OneToMany(mappedBy = "bag")
    private List<Item> itemList; 
    /**
    getItemList() 호출 시, 아래 SQL 구문이 생성된다.
    select 
     ~
     album.item_id as album_item_id,
     {sub 타입 개수만큼 item_id 반복}
    from item
    left outer join album on item.id = album.item_id
    {sub 타입 개수만큼 left outer join 반복}
    **/
}

@Entity
@Inheritance(strategy = InheritanceType.JOINED) // JOIN 전략을 사용
@DiscriminatorColumn(name = "DTYPE") // Type 컬럼명 지정
public abstract class Item {
    @Id @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;
    ...
}

@Entity
@DiscriminatorValue("A") // Type 컬럼의 값이 "A"로 입력됨을 의미
public class Album extends Item {
    private String artist;
}

@Entity
@DiscriminatorValue("M") // Type 컬럼의 값이 "M"로 입력됨을 의미
public class Movie extends Item {
    private String actor;
}

최대 장점은 정규화 됐다는 것, 최대 단점은 단일 조회 시 JOIN이 기본적으로 한 번 일어난다는 점이다.(Insert는 두번) 다만, 충분히 많은 양의 데이터가 입력돼있다면, 단일 테이블 대비 오히려 성능이 더 좋아질 수 있다.

그런데, 객체는 리팩토링을 통해서 공통 부분을 추출해내는 등 자유롭게 변경될 수 있지만, 데이터는 마이그레이션이 필요하기 때문에, 공통 부분을 뽑아낸다는 것이 쉽지 않다는 문제가 있다.

때문에 초반에 모든 것이 예측된 케이스가 아니라면 JOIN 전략을 사용할 일이 잘 없을 것 같다.

단일 테이블 전략

단일 테이블 전략이란, 각각 sub type 테이블들로 분리해뒀던 컬럼들을 super type 테이블에 모두 포함시키는 것이다. 실제 저장된 sub type마다 사용하지 않는 컬럼(다른 sub type이 쓸 영역)들에는 null이 들어가게 된다.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) // SINGLE_TABLE 전략을 사용
@DiscriminatorColumn(name = "DTYPE") // Type 컬럼명 지정, 필수
public abstract class Item {
    @Id @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;
    /*
    사족으로, id의 타입으로 Wrapper Class인 Long을 사용하는 이유는, 영속화 되기 전에는 id가 null일 수 있기 때문이다. 
    long으로 해도 잘 동작하긴 하지만, id가 연산용 값도 아니므로 성능 걱정 하지 말고 오해의 소지 자체를 없애는게 좋다.
    */
    ...
}

@Entity
@DiscriminatorValue("A") // Type 컬럼의 값이 "A"로 입력됨을 의미
public class Album extends Item {
    private String artist;
}

@Entity
@DiscriminatorValue("M") // Type 컬럼의 값이 "M"로 입력됨을 의미
public class Movie extends Item {
    private String actor;
}

장점: 당장 쓰기 편하고 빠르다

단점: 데이터 관점에서 아주 좋지 않다. 사용하지 않는 컬럼들이 많이 있고, 공통 컬럼을 제외한 나머지가 모두 nullable이기 때문이다.

단일 테이블 전략으로 진행하다가, 특이점들에 대한 경험이 충분히 모인다면 JOIN 전략으로 변경하는 것도 좋을 것 같다. 보통 데이터 마이그레이션은 두가지 방식으로 수행된다.

  1. 서버 내려놓고 데이터 복제
  2. 듀얼 writing으로 무중단 데이터 복제

웹 개발을 하며, 서버 내려놓고 데이터 복제 방식을 사용한다는 경우는 본 적이 없다. 아마도 대부분 듀얼 writing 방식을 사용할 것인데, 이 경우에 JPA에서 대응할 수 있는 방법이 있는지 궁금하다.

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

이 전략은 실제 데이터들은 모두 별도 테이블에 저장하지만, 클래스 상에서는 상속 구조인 것 처럼 보이게 하는 방식이다. 테이블이 모두 별개이다 보니, 공통 부분에 대한 내용이 보장되지 않고, 관리하기가 힘들다. 쿼리도 전부 다 UNION으로 날아가게 되서, 성능 로스가 극심하다.

설명은 패스~

@MappedSuperClass

구현 클래스마다 테이블 전략과 다른점은 단 하나, SuperClass가 Entity이냐 아니냐 이다. Entity로 기능하는 InheritanceType.TABLE_PER_CLASS는 다른 클래스와 연관관계를 맺을 수 있지만, @MappedSuperClass는 그럴 수 없다. 단순히 매핑 정보를 공통으로 쓸 수만 있고, 추상화된 단위로 데이터를 읽기, 쓰기, 관계 맺기 등을 할 수는 없다.

책에서는 등록일자, 수정일자, 등록자, 수정자 같은 공통 컬럼들을 다루는데 좋다고는 하지만, Java에서는 단일 상속만 지원되므로, 매우 좋지 않은 예시로 느껴진다. 중복 설정 줄여주는 건 좋은데, 한번 밖에 못하는 상속 씩이나 써가며 줄여야할지는..? 이런 경우는 차라리 Embedded 컬럼을 쓰는게 어떨까 싶다.(자세한건 공부를 해봐야 알겠지만)

결론은 왜 있는 기능인지 잘 모르겠다.

사용법은 단순하니 패스한다.

복합키와 식별관계 매핑

이 장은 복합키와 식별, 비식별 관계 매핑에 대한 이야기를 다룬다. 비식별 관계는 이전 장들에서 이미 다뤄졌기 때문에 생략한다.

복합 키 식별 관계

식별 관계의 연쇄(부모 자식 손자 ...)는 필연적으로 복합 키를 만들게 되고, 때문에 비식별 관계를 통한 단일 키 방식이 추천된다. 책에서는 식별 관계에도 장점이 있다고는 하지만.. FK도 안거는 현업에는 씨알도 먹히지 않는 이야기인 것 같다. 특히, 여러군데서 관계를 맺게 되는 중심 테이블들에 식별 관계가 사용된 순간.. 지옥헬 테이블 구조가 완성되게 된다.

예제는 책에 있으니 패스하고, 복합 키를 다루는 IdClass와 EmbeddedId에 대한 고민만 써볼까 한다.

IdClass는 Entity에 flat한 attribute를 제공하지만, EmbeddedId는 객체로 묶여진 하나의 attribute를 제공한다. IdClass의 단점은 신규 생성과 조회의 방식이 일관되지 않는다는 점이다. EmbeddeId의 단점은 entity.getId().getParentId() 라는 방식으로, 언뜻 보기에는 이해하기 힘든 접근법을 사용해야한다는 점이다.

2depth까지 연관관계 까지는 그나마 EmbeddedId가 나아보이지만(조금이라도 명시적이라서), 3depth로 가는 순간 도긴개긴이다. 3depth까지는 절대로 안간다는 전제 하에서는 EmbeddedId가 좀 낫다.

그놈이 그놈이라면, 상쇄 가능한 단점을 지닌 EmbeddeId를 사용하는게 그나마 낫다는 생각이 들었다. 아래처럼 처리하면 child.getParentId()를 바로 호출할 수 있다.

public interface IdClass {
    int getParentId();
    int getChildId();
}
@Embeddable
public class IdClassImpl implements Serializable, IdClass {
    ...
}
@Entity
public class Child implements IdClass {
    @Delegate //lombok 애노테이션
    @EmbeddedId
    private IdClassImpl id;

    ...

}

아직 테스트를 안해봐서 Child extends IdClassImpl이 가능한지는 잘 모르겠지만, 가능하다면 굳이 IdClass를 interface로 빼는 번거로움은 사라질 것 같다.

개인적으로 복합 키를 지닌 Entity가, 그 자체로 Id 형태로 추상화 될 수 있을때의 효용이 매우 뛰어났던 것으로 기억한다. 의미상으로는 'Entity' is a 'Id'이기 때문에, 추상화가 전혀 어색하지 않다.

일대일 식별 관계

는 장단을 따질 필요 없는 단순 예제라서 패스~

조인 테이블

JOIN 전용 매핑 정보를 저장하는 테이블을 다루는 방법이다. 요기도 마찬가지로 단순 예제들의 집합이므로 패스~

엔티티 하나에 여러 테이블 매핑

말 그대로 엔티티 하나에 여러 테이블을 매핑하는 것인데, 1대1 식별관계와 결과적으로는 동일하다. 물론 객체의 형태는 약간 다르다. 외부에서 사용하기에는(getter) '엔티티하나에 여러 테이블 매핑'이 좀 더 자연스러운 것 같다. 다만, 항상 동시에 두 테이블을 조회하므로 나눠놓음으로 얻을 수 있던 이득이 사라진다. 물론 DB 관점에서만 보자면 이득이 있을 수도 있겠다(뭔진 잘 모르겠지만..)

ERD(를 대충 표현)

Board{
    board_id(PK),
    title varchar(256)
}

BoardDetail{
    board_id(PK, FK),
    content varchar(1024)
}

일대일 식별관계(사용할 때 board.getBoardDetail().getContent() 방식이라 약간 애매한 느낌이다.)

@Entity
public class Board {
    @Id @GeneratedValue
    @Column(name = "BOARD_ID")
    private Long id;
    private String title;

    @OneToOne(mappedBy = "board)
    private BoardDetail boardDetail;
}

@Entity
public class BoardDetail {
    @Id
    private Long boardId;

    @MapsId
    @OneToOne
    @JoinColumn(name="BOARD_ID")
    private Board board;

    private String content;
}

엔티티 하나에 여러 테이블 매핑(사용할 때, board.getContent() 방식이라서 조금 자연스러운 느낌이다.)

@Entity
@Table(name="BOARD")
@SecondaryTable(name="BOARD_DETAIL", pkJoinColumns = @PrimaryKeyJoinColumn(name = "BOARD_ID"))
public class Board {
    @Id @GeneratedValue
    @Column(name = "BOARD_ID")
    private Long id;

    private String title;

    @Column(table = "BOARD_DETAIL")
    private String content;
}