흰 스타렉스에서 내가 내리지

복합 키 : 비식별 관계 매핑 본문

JPA

복합 키 : 비식별 관계 매핑

주씨. 2024. 4. 4. 10:29
728x90

 

  • JPA 에서 식별자를 둘 이상 사용하려면 별도의 식별자 클래스를 만들어야 한다.
  • JPA 는 영속성 컨텍스트에 엔티티를 보관할 때, 엔티티의 식별자를 키로 사용한다.
    • 그리고 식별자를 구분하기 위해 equals 와 hashCode 를 사용해서 동등성 비교를 한다.
    • 그런데 식별자 필드가 하나일 떄는 보통 자바의 기본 타입을 사용하므로 문제가 없지만, 식별자 필드가 2개 이상이면 별도의 식별자 클래스를 만들고 그곳에 equals 와 hashCode 를 구현해야 한다.
  • JPA 는 복합키를 지원하기 위해 @IdClass 와 @EmbeddedId 2가지 방법을 제공한다.
    • @IdClass 는 관계형 데이터베이스에 가까운 방법이고, @EmbeddedId 는 좀 더 객체지향에 가까운 방법이다. 

 

 

# @IdClass

  • 복합 키를 매핑하기 위해 식별자 클래스를 별도로 만들어야 한다.
@Entity
@IdClass(ParentId.class)
public class Parent {
    @Id @Column(name = "PARENT_ID1")
    private String id1;     // ParentId.id1 과 연결
    @Id @Column(name = "PARENT_ID2")
    private String id2;     // ParentId.id2 와 연결
}
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 int hashCode() {
        return Objects.hash(id1, id2);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;

        if (!(o instanceof ParentId))
            return false;

        ParentId that = (ParentId) o;
        return Objects.equals(id1, that.id1) && Objects.equals(id2, that.id2);
    }
}

 

  • 식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야 한다.
    • 예제의 Parent.id1 과 ParentId.id1, 그리고 Parent.id2 와 ParentId.id2 가 같다
  • Serializable 인터페이스를 구현해야 한다
  • equals, hashCode 를 구현해야 한다.
  • 기본 생성자가 있어야 한다
  • 식별자 클래스는 Public 이어야 한다. 
@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;
}
  • 부모 테이블의 키본키 컬럼이 복합 키이므로 자식 테이블의 외래 키도 복합 키이다.
  • 외래키 매핑 시 여러 컬럼을 매핑해야 하므로 @JoinColumns 어노테이션을 사용하고 각각의 외래 키 컬럼을 @JoinColumn 으로 매핑한다.
  • 참고로 위 예제처럼 @JoinColumn 의 name 속성과 referencedColumnName 속성의 값이 같으면 referencedColumnName 은 생략해도 된다. 

 

 

 

# @EmbeddedId

  • @IdClass 가 데이터베이스에 맞춘 방법이라면 @EmbeddedId 는 좀 더 객체지향적인 방법이다.
@Entity
public class Parent {
    @EmbeddedId
    private ParentId id;
}
@Embeddable
public class ParentId implements Serializable {
    @Column(name = "PARENT_ID1")
    private String id1;
    @Column(name = "PARENT_ID2")
    private String id2;

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

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;

        if (!(o instanceof ParentId))
            return false;

        ParentId that = (ParentId) o;
        return Objects.equals(id1, that.id1) && Objects.equals(id2, that.id2);
    }
}
  • Parent 엔티티에서 식별자 클래스를 직접 사용하고 @EmbeddedId 어노테이션을 적어주면 된다
  • @IdClass 와는 다르게 @EmbeddedId 를 적용한 식별자 클래스는 식별자 클래스에 기본 키를 직접 매핑한다. 

 

 

# 복합 키와 equals(), hashCode()

  • 복합키는 equals() 와 hashCode 를 필수로 구현해야 한다. 
  • ParentId id1 = new ParentId(); ParentId id2 = new ParentId(); id1.equals(id2) → ?
    • equals() 를 적절이 오버라이딩 하지 않았다면 거짓이다.
    • 자바의 모든 클래스는 기본으로 Object 클래스를 상속받는데 이 클래스가 제공하는 기본 equals()는 인스턴스 참조 값 비교인 == 비교 (동일성 비교)를 하기 때문이다.
  • 영속성 컨텍스트는 엔티티의 식별자를 키로 사용해서 엔티티를 관리한다.
  • 그리고 식별자를 비교할 떄 equals() 와 hashCode() 를 사용한다
    • 따라서 식별자 객체의 동등성이 지켜지지 않으면 예상과 다른 엔티티가 조회되거나 엔티티를 찾을 수 없는 등 영속성 컨텍스트가 엔티티를 관리하는데 심각한 문제가 발생한다. 

 

 

# 장단점

  • @EmbeddedId 가 @IdClass 와 비교해서 더 객체지향적이고 중복도 없어서 좋아보리긴 하지만, 특정 상황에 JPQL 이 더 길어질 수 있다. 
em.createQuery("select p.id.id1, p.id.id2 from Parent p");	// EmbeddedId
em.createQuery("select p.id1, p.id2 from Parent p");	// Idclass

 

 

 

 

복합 키에는 @GenerateValue 를 사용할 수 없다

'JPA' 카테고리의 다른 글

프록시와 즉시로딩/지연로딩  (0) 2024.04.08
일대일 식별 관계  (0) 2024.04.04
식별 관계 vs 비식별 관계  (0) 2024.04.04
@MappedSuperclass  (1) 2024.04.03
연관관계 편의 메소드  (0) 2024.04.02