ORM(JPA)

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

hjhello423 2020. 2. 4. 21:45

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 프로그래밍, 김영한, 에이콘

반응형