[JPA] 복합 키(Composite Key) vs 대리 키(Surrogate Key)

aqualung·2025년 2월 12일
post-thumbnail

복합 키(Composite Key)

JPA에서 보통 두 가지 방법으로 복합키를 매핑한다고 합니다.

예시로 엔티티 OrderItem를 각각 외래키로 가지고 두 키를 복합키로 가지는 OrderItem이라는 매핑테이블이 있다고 가정하고 설명해 보겠습니다.

@IdClass

복합키로 식별할 클래스가 필요합니다.

인터페이스 Serializable를 구현하고 equals()hashcode()를 오버라이드해 줍니다.

/* OrderItemId */
@Getter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class OrderItemId implements Serializable {

    private Long order; // OrderItem의 필드 이름과 동일해야함
    private Long item;
}

@IdClass어노테이션으로 만들어둔 클래스를 매핑할 수 있습니다.

@Entity
@IdClass(OrderItemId.class) // 복합키
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class OrderItem {

    @Id
    @ManyToOne
    @JoinColumn(name = "order_id")
    private Order order;

    @Id
    @ManyToOne
    @JoinColumn(name = "item_id")
    private Item item;
}

@EmbeddedId

마찬가지로 복합키 클래스를 만듭니다.
@Embeddable 어노테이션을 붙여줍니다.

/* OrderItemId */
@Embeddable
@Getter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class OrderItemId implements Serializable {

    private Long orderId;
    private Long itemId;
}

@EmbeddedId 어노테이션을 가진 필드를 기본키로 사용할 수 있습니다.

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class OrderItem {

    @EmbeddedId
    private OrderItemId id; // 복합키

    @ManyToOne
    @MapsId("orderId")
    @JoinColumn(name = "order_id")
    private Order order;

    @ManyToOne
    @MapsId("itemId")
    @JoinColumn(name = "item_id")
    private Item item;
}

단점

JPA에서는 두 개 이상의 컬럼을 키로 사용하기 위해 다소 복잡한 설정이 필요하고 코드와 클래스가 많아지게 됩니다.

이는 복합키를 가지는 엔티티가 다른 엔티티와 연관관계를 가질 때에도 나타납니다.
@IdClass를 사용한 예시를 보겠습니다.

@Entity
class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "order_id")
    private Long id;

    @ManyToOne
    @JoinColumns({
        @JoinColumn(name = "order_id", referencedColumnName = "order_id"),
        @JoinColumn(name = "product_id", referencedColumnName = "product_id")
    })
    private OrderItem orderItem;
}

@JoinColumns를 통해 복합키로 사용되는 컬럼을 모두 매핑해주어야 합니다.

그럼 복합키를 사용하는 대신 대리키를 사용하면 어떨까요?


대리 키(Surrogate Key)

엔티티를 하나의 컬럼으로만 식별하기 때문에 복합키를 위한 클래스를 작성할 필요가 없습니다. @Id@GeneratedValue로 엔티티마다 고유한 키를 갖도록 해주었습니다.

import jakarta.persistence.*;

@Entity
class OrderItem {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;  // 대리키

    @ManyToOne
    @JoinColumn(name = "order_id")
    private Order order;

    @ManyToOne
    @JoinColumn(name = "item_id")
    private Item item;
}

대리키를 위한 컬럼이 늘어났지만 개발자의 입장에서는 규칙에 따라 클래스를 만들거나 별도의 매핑이 필요하지 않게 되어 코드가 깔끔해졌습니다.

다른 엔티티와의 연관관계를 매핑할 때도 훨씬 깔끔해집니다.

@Entity
class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "order_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "order_item_id")
    private OrderItem orderItem;
}

결론

개인적으로 JPA에서는 복합키보다 대리키를 사용하는 게 이득이 많다고 느꼈습니다. 하지만 데이터의 무결성이나 인덱스효율(참고) 등이 중요하다면 복합키가 더 유리할 수 있다고 합니다.

0개의 댓글