JPA에서 보통 두 가지 방법으로 복합키를 매핑한다고 합니다.
예시로 엔티티 Order와 Item를 각각 외래키로 가지고 두 키를 복합키로 가지는 OrderItem이라는 매핑테이블이 있다고 가정하고 설명해 보겠습니다.
복합키로 식별할 클래스가 필요합니다.
인터페이스 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;
}
마찬가지로 복합키 클래스를 만듭니다.
@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를 통해 복합키로 사용되는 컬럼을 모두 매핑해주어야 합니다.
그럼 복합키를 사용하는 대신 대리키를 사용하면 어떨까요?
엔티티를 하나의 컬럼으로만 식별하기 때문에 복합키를 위한 클래스를 작성할 필요가 없습니다. @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에서는 복합키보다 대리키를 사용하는 게 이득이 많다고 느꼈습니다. 하지만 데이터의 무결성이나 인덱스효율(참고) 등이 중요하다면 복합키가 더 유리할 수 있다고 합니다.