본문 바로가기
ORM(JPA)

[JPA] jpa에서 복합키와 식별관계 매핑 하기

by hjhello423 2020. 2. 4.

DB의 설계상에서 key를 단일 키만 사용하는 경우는 거의 없습니다.

대부분 복합 key를 사용하여 DB 설계를 하고 있습니다.

그렇다면 JPA에서 복합 키를 어떻게 표현할 수 있을까요?

그 방법에 대해 차근차근 살펴보겠습니다.


식별, 비식별 관계

DB에서는 FK(외래 키)가 기본 키에 포함되는지 여부에 따라 식별, 비식별 관계로 구분됩니다.

보통 비식별 관계를 주로 사용하고 필요한 상항에서 식별 관계를 사용합니다.

  • 식별 관계 : 부모 테이블의 기본 키를 받아 자식 테이블의 기본 키 + 외래 키로 사용
  • 비식별 관계 : 부모 테이블의 기본 키를 받아서 자식 테이블의 외래 키 로 사용
    • 필수적 비식별 관계 : 외래 키에 NULL을 허용하지 않음
    • 선택적 비식별 관계 : 외래 키에 NULL을 허용

복합 키를 표현할 때는 JPA에서는 식별자 클래스를 만들어야 합니다.

그리고 식별자 클래스는 @IdClass, @EmbededId 두 개의 어노테이션 중 하나를 선택해서 사용하면 됩니다.

간단하게 말하자면 @IdClass는 DB에 가까운 방법, @EmbededId는 객체지향에 가까운 방법입니다.

아래에서 설명할 식별자 클래스의 equals(), hashCode()영속성 컨텍스트가 식별자를 비교할때 사용하기 때문에 반드시 오버라이딩 해야합니다.

비식별 관계 매핑

@IdClass 사용

@Entity
@IdClass(ParentId.class)
public class Parent {

    @Id
    @Column(name = "parent_id1")
    private String id1;

    @Id
    @Column(name = "parent_id2")
    private String id2;

    private String name;
}

 

public class ParentId implements Serializable {

    private String id1;
    private String id2;

    public ParentId() {
    }

    public ParentId(String id1, String id2) {
        this.id1 = id1;
        this.id2 = id2;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ParentId parentId = (ParentId) o;
        return id1.equals(parentId.id1) &&
                id2.equals(parentId.id2);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id1, id2);
    }
}

 

@Entity
public class Child {

    @Id
    private String id;

    @ManyToOne
    @JoinColumns({
            @JoinColumn(name = "parent_id1", referencedColumnName = "parent_id1"),
            @JoinColumn(name = "parent_id2", referencedColumnName = "parent_id2")
    })
    private Parent parent;
}

코드를 순서대로 살펴보겠습니다.

복합 키를 사용하기 위해선 식별자 클래스를 만들어야 합니다. 해서 ParentId 식별자 클래스를 생성하였습니다.

Parent에서 @IdClass(ParentId.class)를 선언하여 식별자 클래스를 지정하였습니다.

식별자 클래스에는 몇 가지 특징이 있습니다.

  • 식별자 클래스의 필드명과 엔티티에서 사용하는 식별자의 필드명이 동일해야 합니다.
    Parent.id1, ParentId.id1이 서로 동일한 필드명을 사용하고 있습니다.
  • Serializable 인터페이스를 구현해야 합니다.
  • equals(), hashCode()를 구현해야 한다.
  • 기본 생성자를 선언해야 합니다.
  • 클래스의 접근 제한자는 public이어야 합니다.

Child에서는 복합 키 매핑을 위해 @JoinColumns를 이용하고 각각의 key는 @JoinColumn으로 매핑하였습니다.

referencedColumnName 속성이 name 속성과 동일할 때 생략 가능하나, 미 지정했을 때 @JoinColumns에 선언된 @JoinColumn의 순서가 바뀌어 저장 될수 있습니다. 꼭 referencedColumnName 속성을 지정하길 권장 합니다. (링크)

@EmbeddedId 사용

@Entity
public class Parent {

    @EmbeddedId
    private ParentId id1;

    private String name;
}

 

@Embeddable
public class ParentId implements Serializable {

    @Column(name = "parent_id1")
    private String id1;
    @Column(name = "parent_id2")
    private String id2;

    public ParentId() {
    }

    public ParentId(String id1, String id2) {
        this.id1 = id1;
        this.id2 = id2;
    }

    @Override
    public boolean equals(Object o) { ... }

    @Override
    public int hashCode() { ... }
}

Parent에서 식별자 클래스 타입의 필드를 생성하고 @EmbeddedId을 사용하여 비식별 관계 매핑을 해주었습니다.

@IdClass와는 다르게 @EmbeddedId를 적용한 식별자 클래스는 식별자 클래스에 기본 키를 직접 매핑 합니다.

@EmbeddedId를 사용할때는 아래의 조건이 있습니다.

  • 식별자 클래스에 @Embeddable을 사용해야 한다.
  • Serializable 인터페이스를 구현해야 합니다.
  • equals(), hashCode()를 구현해야 한다.
  • 기본 생성자를 선언해야 합니다.
  • 클래스의 접근 제한자는 public이어야 합니다.




식별 관계 매핑

@IdClass 사용

@Entity
public class Parent {

    @Id
    @Column(name = "parent_id")
    private String id1;

    private String name;
}

 

@Entity
@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;
}

 

public class ChildId implements Serializable {

    private String parent; //Child.parent 매핑
    private String childId; //Child.childId 매핑

    @Override
    public boolean equals(Object o) { ... }

    @Override
    public int hashCode() { ... }
}

 

@Entity
@IdClass(GrandChildId.class)
public class GrandChild {

    @Id
    @ManyToOne
    @JoinColumns({
            @JoinColumn(name = "parent_id", referencedColumnName = "parent_id"),
            @JoinColumn(name = "child_id", referencedColumnName = "child_id")
    })
    private Child child;

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

    private String name;
}

 

public class GrandChildId implements Serializable {
    private ChildId child; //GrandChild.child 매핑
    private String id; //GrandChild.id 매핑

    @Override
    public boolean equals(Object o) { ... }

    @Override
    public int hashCode() { ... }
}

식별 관계는 기본 키와 외래 키를 같이 매핑 해야 합니다. @Id와 연관관계 매핑인 @ManyToOne을 같이 사용하여 매핑 하였습니다.

@EmbeddedId 사용

@Entity
public class Parent {

    @Id
    @Column(name = "parent_id")
    private String id1;

    private String name;
}

 

@Entity
public class Child {

    @EmbeddedId
    private ChildId id;

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

    private String name;
}

 

@Embeddable
public class ChildId implements Serializable {

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

    @Column(name = "child_id")
    private String id;

    @Override
    public boolean equals(Object o) { ... }

    @Override
    public int hashCode() { ... }
}

 

@Entity
public class GrandChild {

    @EmbeddedId
    private GrandChildId id;

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

    private String name;
}

 

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

    @Column(name = "grandchild_id")
    private String id;

    @Override
    public boolean equals(Object o) { ... }

    @Override
    public int hashCode() { ... }
}

@EmbeddedId는 식별 관계로 매핑할때 @MapsId를 사용하면 됩니다.

@MapsId는 외래 키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻입니다. 속성값은 @EmbeddedId를 사용한 필드명을 지정합니다.


참고

자바 ORM 표준 JPA 프로그래밍, 김영한, 에이콘

반응형

댓글