EduClass Project

[Project] N:M 매핑에서 복합 키(Composite Key)를 사용할 때 주의할 점

sagecode 2025. 3. 5. 17:32

 

복합키(Composite Key) vs 고유 Id

 

JPA에서 N:M 관계를 매핑할 때, 중간 테이블(연결 테이블)에 고유 ID(@Id)를 넣을 것인지, 복합 키(@EmbeddedId)를 사용할 것인지 고민하는 경우가 많다.

 

처음에는 "그냥 @Id를 넣고 auto_increment 하면 되지 않나?"라고 생각할 수 있지만, 이 방식은 데이터 무결성과 성능 문제를 초래할 수 있다.본 글에서는 복합 키(Composite Key)를 사용할 때의 장점과 @Id 방식이 가지는 문제점을 분석해보겠다.

 

N:M 관계에서 중간 테이블의 역할

  • ProblemSet(문제지)와 Problem(문제)은 N:M 관계이다.
  • 따라서 ProblemSetToProblem이라는 중간 테이블이 필요하다.
  • 이때, 이 테이블의 고유 식별자(PK)를 어떻게 설정해야 할까?
    • @Id(고유 ID를 생성하는 방식)
    • @EmbeddedId(복합 키를 이용하는 방식)

 

고유 Id를 사용했을 경우?

@Entity
@Table(name = "problem_set_to_problem")
public class ProblemSetToProblem {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 자동 증가 ID
    private Long id;

    @ManyToOne
    @JoinColumn(name = "problem_set_id", nullable = false)
    private ProblemSet problemSet;

    @ManyToOne
    @JoinColumn(name = "problem_id", nullable = false)
    private Problem problem;
}

 

이렇게 고유 Id를 사용할 경우에 문제점들이 존재한다.

(1) 데이터 무결성(Integrity) 문제

  • problem_set_id, problem_id 조합이 중복 삽입될 수 있다.
INSERT INTO problem_set_to_problem (problem_set_id, problem_id) VALUES (1, 5);
INSERT INTO problem_set_to_problem (problem_set_id, problem_id) VALUES (1, 5); -- 중복 가능 ❌

 (2) 쿼리의 복잡성 증가

  • problem_set_id와 problem_id로 데이터를 조회하려면 WHERE 조건이 추가로 필요하다.
  • 반면, @EmbeddedId를 사용하면 findById()로 간단하게 조회 가능하다.

(3) 비즈니스 로직 관점에서 의미 없는 ID

  • 이 테이블은 "어떤 문제지가 어떤 문제를 포함하는가" 를 나타내는 역할만 한다.
  • 하지만, id를 추가하면 문제지-문제 간의 관계가 아닌, 단순한 테이블 row 번호만을 가지게 된다.
  • problem_set_id + problem_id 조합이 이 테이블의 진짜 고유 식별자가 되어야 한다.

 

복합 키(@EmbeddedId)를 사용하면 어떻게 해결될까?

@Entity
@Table(name = "problem_set_to_problem")
public class ProblemSetToProblem {

    @EmbeddedId
    private ProblemSetToProblemId id;

    @ManyToOne
    @MapsId("problemSetId")
    @JoinColumn(name = "problem_set_id", nullable = false)
    private ProblemSet problemSet;

    @ManyToOne
    @MapsId("problemId")
    @JoinColumn(name = "problem_id", nullable = false)
    private Problem problem;
}
@Embeddable
public class ProblemSetToProblemId implements Serializable {
    private Long problemSetId;
    private Long problemId;
}

 

복합키 방식의 장점

  • 데이터 무결성 유지: 동일한 problem_set_id, problem_id 조합의 중복 입력 방지
  • 더 쉬운 조회 가능: findById()로 복합 키 조회 가능
  • 테이블 자체의 의미를 더 명확하게 표현

 

결론 - 언제 @EmbeddedId를 사용할까?

복합 키(@EmbeddedId)를 사용하는 것이 좋은 경우

  1. 연결 테이블(Bridge Table)에서 N:M 관계를 매핑할 때
  2. 테이블 자체가 특정 두 개의 외래 키 조합으로 유일성을 가져야 할 때
  3. 고유 ID가 의미 없는 경우 (단순히 row를 구분하기 위해 ID를 만드는 것이 비효율적일 때)

고유 ID(@Id)를 사용하는 것이 좋은 경우

  1. 테이블이 단순한 연결 테이블이 아니라 별도의 속성을 가질 때
    • 예: StudentTest 같은 테이블에서 추가적인 score(점수) 같은 속성이 필요한 경우.
  2. 테이블이 독립적인 엔티티로 사용될 때
    • 예: 단순한 N:M 관계가 아니라, 그 자체가 비즈니스 로직에서 중요한 역할을 할 경우.