jpa에서 외래키이자 복합키인 값 다루기

최창효·2023년 9월 25일
1
post-thumbnail

테이블을 설계하다보면 ManyToMany관계를 풀기 위한 연결 테이블을 생성하는 경우가 많습니다. 그리고 이 연결 테이블은 식별 관계로 양쪽의 Id를 가져와 자신의 PK로 활용하기도 합니다. 아래와 같이 말이죠.


이러한 테이블 설계를 JPA에서는 어떻게 구현할 수 있는지 알아보겠습니다.

복합키 만들기

우선 두 개의 값을 하나의 key로 활용하려면 복합키를 만들어야 합니다. JPA에서는 @EmbeddedId는 별도의 객체를 Id로 사용하게 해줌으로써 간단하게 복합키를 구현할 수 있게 해줍니다.

상품

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

주문

@Entity(name="orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private int totalPrice;
}

상품주문 복합키

@Embeddable
public class ProductOrderId implements Serializable {
    private Long productId;
    private Long orderId;
}

상품주문

@Entity
public class ProductOrder {
    @EmbeddedId
    private ProductOrderId id;
    private int quantity;
}

이렇게 테이블을 생성하면 order_id와 product_id를 묶어서 primary_key로 설정하는 걸 확인할 수 있습니다.

하지만 지금은 order_id라는 변수와 product_id라는 변수로 key를 만들었을 뿐, 이 값이 order테이블과 product테이블의 PK라는 사실을 모릅니다.

연관관계 설정

JPA에서는 테이블 사이의 관계를 설정하기 위해 @ManyToOne, @OneToMany등의 다양한 연관관계 매핑을 지원합니다.
우선 복합키를 고려하지 않고 기존의 방식대로 연관관계를 매핑해 보겠습니다.

@Entity
public class ProductOrder {
    @EmbeddedId
    private ProductOrderId id;
    
    @ManyToOne
    @JoinColumn(name = "order_id")
    private Order order;
    
    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;
    private int quantity;
}

테이블을 생성을 시도하면 아래와 같은 에러가 발생합니다.

에러는 동일한 이름의 변수가 여러개 있다는 의미로, ProductOrderId에 있는 orderId와 @ManyToOne으로 만들 FK인 order_id가 모두 결국에는 order_id라는 동일한 이름을 가진다는 얘기입니다.

이름이 같은게 문제가 된다면 joinColumn의 이름을 한번 바꿔보겠습니다.

@Entity
public class ProductOrder {
    @EmbeddedId
    private ProductOrderId id;
    
    @ManyToOne
    @JoinColumn(name = "my_order_id")
    private Order order;
    
    @ManyToOne
    @JoinColumn(name = "my_product_id")
    private Product product;
    private int quantity;
}
  • Order테이블과 매핑할 FK의 이름을 order_id가 아닌 my_order_id로, product_id도 my_product_id로 변경했습니다.

이렇게 하면 테이블은 무사히 생성됩니다. 하지만 생성 쿼리를 살펴보면 뭔가 이상합니다.

order_id와 my_order_id, product_id와 my_product_id가 각각 존재하고 있습니다. 즉, product_order의 복합키로 활용되는 order_id와 order_table과 관계를 맺는 FK로써의 my_order_id가 따로 존재하는 것입니다.

이것 역시 우리가 원하는 형태가 아닙니다. 우리는 orders테이블의 FK인 order_id가 복합키로도 활용되길 원합니다.

MapsId

@EmbeddedId로 식별관계를 만들 때는 @MapsId어노테이션을 활용해야 합니다. @MapsId는 해당 FK가 복합키(@EmbeddedId)의 어떤 변수인지를 설정할 수 있습니다.

정리해 보면 이런 관계인거죠.

다음과 같이 사용해 봅시다.

@Entity
public class ProductOrder {
    @EmbeddedId
    private ProductOrderId id;
    
    @MapsId("orderId")
    @ManyToOne
    @JoinColumn(name = "order_id")
    private Order order;
    
    @MapsId("productId")
    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;
    private int quantity;
}

다음과 같은 쿼리로 테이블이 생성됩니다.

order_id와 product_id는 각각 Order테이블, Product테이블과 관계를 맺는 FK면서 동시에 product_order의 PK인, 우리가 원하는 결과를 얻었습니다!

References

profile
기록하고 정리하는 걸 좋아하는 개발자.

0개의 댓글