복합키 식별 관계 매핑

부모, 자식, 손자까지 계속 기본 키를 전달하는 식별관계가 있다고 치자 식별 관계에서 자식 테이블은 부모 테이블의 기본 키를 포함해서 복합 키를 구성해야 하므로 @IdClass 나 @EmbeddedId를 사용해서 식별자를 매핑해야 한다. (일대일 관계랑은 약간 다르다.)

@IdClass

@Entity
@Data
public class Parent {
  @Id
  @Column(name = "PARENT_ID")
  private String id;

  private String name;

}

@Entity
@Data
@IdClass(ChildId.class)
public class Child {
  @Id
  @ManyToOne
  @JoinColumn(name = "PARENT_ID")
  private Parent parent;

  @Id
  @Column(name = "CHILD_ID")
  private String childId;

  private String name;
}

@Data
public class ChildId implements Serializable {

  private String parent;
  private String childId;
}

@Entity
@Data
@IdClass(GrandChildId.class)
public class CrandChild {
  @Id
  @ManyToOne
  @JoinColumns({
    @JoinColumn(name = "PARENT_ID"),
    @JoinColumn(name = "CHILD_ID"),
  })
  private Child child;

  @Id
  @Column(name = "GRANDCHILD_ID")
  private String id;

  private String name;
}

@Data
public class GrandChildId implements Serializable{
  private ChildId child;
  private String id;
}

식별 관계는 기본 키와 외래 키를 같이 매핑 해야 한다. 따라서 식별자 매핑인 @Id와 연관관계 매핑인 @ManyToOne을 같이 사용하면 된다.

@Id
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;

Child 엔티티의 Parent 필드를 보면 @Id로 기본 키를 매핑하면서 @ManyToOne과 @JoinColumn으로 외래 키를 같이 매핑한다.

EmbeddedId와 식별 관계

@Entity
@Data
public class Parent {
  @Id
  @Column(name = "PARENT_ID")
  private String id;

  private String name;

}

@Entity
@Data
public class Child {

  @EmbeddedId
  private ChildId id;

  @MapsId("parentId") //ChildId.parentId 매핑
  @ManyToOne
  @JoinColumn(name = "PARENT_ID")
  private Parent parent;

  private String name;
}

@Data
@Embeddable
public class ChildId implements Serializable {

  private String parentId; //@MapsId("parentId") 로 매핑
  private String childId;
}

@Entity
@Data
public class CrandChild {

  @EmbeddedId
  private GrandChildId id;

  @MapsId("childId") // GrandChildId.childId 매핑
  @ManyToOne
  @JoinColumns({
    @JoinColumn(name = "PARENT_ID"),
    @JoinColumn(name = "CHILD_ID"),
  })
  private Child child;

  private String name;
}

@Data
@Embeddable
public class GrandChildId implements Serializable{
  private ChildId childId; //@MapsId("childId") 로 매핑

  @Column(name = "GRANDCHILD_ID")
  private String id;
}

@EmbeddedId는 식별 관계로 사용할 연관관계의 속성에 @MapsId를 사용하면 된다. Child 엔티티 parent 필드를 보자

@MapsId("parentId") //ChildId.parentId 매핑
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;

@IdClass와 다른 점은 @Id 대신에 @MapsId를 사용한 점이다. @MapsId는 외래 키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻이다. @MapsId의 속성 값은 @EmbeddedId를 사용한 식별자 클래스의 기본 키 필드를 지정하면 된다. 여기서는 ChildId의 parentId 필드를 선택했다.

비식별 관계로 구현

@Data
@Entity
public class Parent {
  @Id
  @GeneratedValue
  @Column(name = "PARENT_ID")
  private Long id;

  private String name;
}

@Entity
@Data
public class Child {

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

  private String name;

  @ManyToOne
  @JoinColumn(name = "PARENT_ID")
  private Parent parent;
}

@Data
@Entity
public class GrandChild {

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

  private String name;

  @ManyToOne
  @JoinColumn(name = "CHILD_ID")
  private Child child;
}

식별 관계의 복합 키를 사용한 코드와 비교하면 매핑도 쉽고 코드도 단순하다. 그리고 복합 키가 없으므로 복합 키 클래스를 만들지 않아도 된다.

일대일 식별 관계

일대일 식별관계는 자식 테이블의 기본 키 값으로 부모테이블의 기본 키 값만 사용한다. 그래서 부모 테이블의 기본 키가 복합 키가 아니면 자식 테이블의 기본 키는 복합 키로 구성하지 않아도 된다.

@Entity
@Data
public class Board {

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

  private String title;

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

}

@Entity
@Data
public class BoardDetail {

  @Id
  private Long boardId;

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

  private String content;
}

BoardDetail처럼 식별자가 단순히 컬럼 하나면 @MapsId를 사용하고 속성 값은 비워두면 된다. 이때 @MapsId는 @Id를 사용해서 식별자로 지정한 BoardDetail.boardId와 매핑된다.

private static void save(EntityManager entityManager){
  Board board = new Board();
  board.setTitle("제목");
  entityManager.persist(board);

  BoardDetail boardDetail = new BoardDetail();
  boardDetail.setContent("내용");
  boardDetail.setBoard(board);
  entityManager.persist(boardDetail);
}

위의 코드는 일대일 식별 관계를 저장 하는 코드이다.

식별, 비 식별 관계의 장단점

데이터베이스 설계 관점에서 보면 다음과 같은 이유로 식별 관계보다 비식별 관계를 선호한다.
1. 식별 관계는 부모 테이블의 기본 키를 자식 테이블로 전파하면서 자식 테이블의 기본 키 컬럼이 점점 늘어난다. 그러면 조인할 때 SQL이 복잡해지고 기본 키 인덱스가 불필요하게 커질 수 있다.
2. 식별 관계는 2개 이상의 컬럼을 합해서 복합 기본 키를 만들어야 하는 경우가 많다.
3. 식별 관계를 사용할 때 기본 키로 비지니스 의미가 있는 자연 키 컬럼을 조합하는 경우가 많다. 반면에 비식별관계의 기본 키는 비지니스와 전혀 관계없는 대리 키를 주로 사용한다. 언제든지 요구사항은 변한다. 식별 관걔의 자연 키 컬럼들이 자식에 손자까지 전파되면 변경하기 힘들다.
4. 식별 관계는 부모 테이블의 기본 키를 자식 테이블의 기본 키로 사용하므로 비식별 관계보다 테이블 구조가 유연하지 못하다.

이외에도 몇가지가 더 있다. 하지만 무조건 식별관계가 단점만 있는 것은 아니다. 기본 키 인덱스를 활용하기 좋고, 상위 테이블들의 기본 키 컬럼을 자식, 손자 테이블들이 가지고 있으므로 특정 상황에 조인 없이 하위 테이블만으로 검색을 완료 할 수 있다.

이상으로 식별 비식별 관계에 대해 알아봤다. 다음은 고급매핑 마지막으로 조인 테이블에 대해서 알아보자!

출처 : 자바 ORM 표준 JPA 프로그래밍 (김영한)