[Spring] Spring JPA 복합 키 매핑 - @Embeddable vs @IdClass

1. 소개

 

Spring Data JPA를 사용하다 보면, 단일 필드가 아닌 여러 개의 필드를 조합하여 기본 키(Primary Key)로 사용하는 복합 키(Composite Key) 를 설정해야 할 때가 있다.

 

JPA에서 복합 키를 매핑하는 방법은 크게 두 가지이다.

  1. @EmbeddedId + @Embeddable
  2. @IdClass

이번 글에서는 두 방법을 비교하고, 코드 예제를 통해 직접 실습할 수 있도록 정리해보겠다.


2. @EmbeddedId 방식 (추천 방식)

 

@EmbeddedId + @Embeddable 

 

1. @Embeddable: 복합 키 클래스로 사용할 객체에 선언

2. @EmbeddedId: 엔티티에서 해당 복합 키를 기본 키로 사용

 

(1) 복합 키 정의 (OrderId 클래스)

import java.io.Serializable;
import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class OrderId implements Serializable {
    private Long orderId;
    private Long productId;

    public OrderId() {}

    public OrderId(Long orderId, Long productId) {
        this.orderId = orderId;
        this.productId = productId;
    }
}
 

- @Embeddable: 복합 키 클래스로 사용되며, 개별 엔티티가 아닌 다른 엔티티에 포함될 수 있음
- @EqualsAndHashCode: JPA에서 비교 연산을 정확히 수행하기 위해 필수
- Serializable 구현: JPA에서 복합 키 클래스로 사용하려면 Serializable을 구현해야 함

 

(2) 엔티티 정의 (Order 클래스)

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@IdClass(OrderId.class)
@Getter
@Setter
@NoArgsConstructor
public class OrderEntity {

    @Id
    private Long orderId;

    @Id
    private Long productId;

    private int quantity;

    public OrderEntity(Long orderId, Long productId, int quantity) {
        this.orderId = orderId;
        this.productId = productId;
        this.quantity = quantity;
    }
}
 

- @EmbeddedId: 복합 키 클래스를 기본 키로 사용
- id 필드가 OrderId 객체이므로, 여러 개의 필드를 하나의 객체로 관리 가능

 

이 방식은 객체지향적인 설계를 유지할 수 있어 더 선호되는 방법


3. @IdClass 방식

 

@IdClass 

 

1. 엔티티 클래스 내부에서 개별적으로 @Id를 선언

2. @IdClass를 통해 외부에서 해당 키가 어떤 필드로 구성되는지 명시

 

 코드 예제: @IdClass를 사용한 복합 키 매핑

 

(1) 복합 키 정의 (OrderId 클래스)

import java.io.Serializable;
import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class OrderId implements Serializable {
    private Long orderId;
    private Long productId;

    public OrderId() {}

    public OrderId(Long orderId, Long productId) {
        this.orderId = orderId;
        this.productId = productId;
    }
}

 

- @EqualsAndHashCode: 복합 키 비교를 위해 반드시 필요
- Serializable 구현: 필수

 

(2) 엔티티 정의 (Order 클래스)

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@IdClass(OrderId.class)
@Getter
@Setter
@NoArgsConstructor
public class OrderEntity {

    @Id
    private Long orderId;

    @Id
    private Long productId;

    private int quantity;

    public OrderEntity(Long orderId, Long productId, int quantity) {
        this.orderId = orderId;
        this.productId = productId;
        this.quantity = quantity;
    }
}

 

- @IdClass(OrderId.class): 복합 키를 사용한다고 명시
- 엔티티 내부에서 @Id 필드를 직접 선언해야 함

 

이 방식은 엔티티 필드 내에서 직접 ID를 선언해야 하므로, 비교적 덜 객체지향적


4. @EmbeddedId vs @IdClass 비교

 

설정 방식 @EmbeddedId 필드에 복합 키 객체 사용 개별 @Id 필드를 엔티티에 선언
ID 클래스 @Embeddable 사용 @IdClass 사용
코드 구조 객체지향적으로 키를 하나의 객체로 관리 SQL 테이블 구조와 유사
장점 코드 재사용성 높음, 유지보수 용이 SQL 구조와 유사하여 직관적
단점 JPA 외부 시스템과의 연동 시 다소 복잡할 수 있음 필드가 엔티티 내부에 있어 응집성 떨어짐

 


 

5. 정리

 

  1. JPA에서 복합 키를 사용하려면 @EmbeddedId 또는 @IdClass
  2. @EmbeddedId는 객체지향적인 방식으로 관리가 용이하고 유지보수가 쉬움.
  3. @IdClass는 SQL과 유사한 구조로 가독성이 좋지만, 코드 응집성이 떨어질 수 있음.