JPA

Spring Data JPA - 외래키(Foreign Key)를 복합 기본키(Composite Primary Key)로 사용하기

Cold Bean 2024. 11. 8. 22:34
728x90

개요

복합 기본키(Composite Primary Key)란 두 개 이상의 컬럼을 묶어 만든 기본키(Primary Key)를 말한다.

복합키는 다대다(N:M) 관계의 테이블의 관계를 풀어주고 공통으로 저장되는 데이터를 다룰 때 사용되는 매핑 테이블에서 사용될 수 있다.

예를 들어, 내가 현재 개발중인 업무평가시스템에서 하나의 팀은 다양한 업무를 담당할 수 있고 하나의 업무를 여러 팀이 담당할 수 있다.

즉, 팀과 업무 테이블은 다대다 관계를 갖고 있다. 그리고 각 업무에 대한 팀별로 평가를 받을 수 있다. 이 때 매핑 테이블 구조는 다음과 같다. 이제 Spring Data JPA에서 외래키로 복합키를 만드는 방법을 알아보자

  • taskId: 업무 테이블의 외래키(foreign key)
  • teamId: 팀 테이블의 외래키(foreign key)
  • point: 해당 업무에 대한 담당 팀 평가 점수

 

개발 환경

  • Java 8
  • Spring Boot 2.7.x
  • Gradle
  • JPA, Hibernate

 

구현

복합키로 사용할 외래키를 가진 Entity

우선 Task(업무), Team(팀)의 Entity 클래스는 다음과 같다.

현재 프로젝트에서는 Task나 Team에서 TaskEvaluation을 조회할 일이 없었기 때문에 단방향 ManyToOne으로 관계를 맺기 때문에 TaskEvaluation을 참조하고 있지 않다.

@Getter
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
@ToString
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity(name = "TASK")
public class TaskEntity extends BaseEntity {

    // 아이디
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;

    // 년도
    @Column(length = 4, nullable = false)
    @Size(min = 4, max = 4)
    private String year;

    // 프로젝트명
    @Column(length = 50)
    @Size(min = 1, max = 50)
    private String projectTitle;

    // 업무명
    @Column(length = 300, nullable = false)
    @Size(min = 1, max = 300)
    private String taskTitle;

    // 업무상태
    @Column(length = 10)
    @Size(min = 1, max = 10)
    private String taskState;

    // 진척도
    @Column(length = 10)
    @NotNull
    private int taskProgress;
}
@Getter
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
@ToString
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity(name = "TEAM")
public class TeamEntity extends BaseEntity {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;

    // 팀명
    @Column(length = 50, nullable = false)
    private String title;
}

 

 

외래키를 복합 기본키로 사용하기

이제 매핑 테이블 역할을 할 TeamEvaluation Entity 클래스를 만들어보자. TeamEvaluation Entity는 팀의 업무 평가 정보를 저장한다.

JPA에서 복합 기본키를 사용하기 위해서는 별도 클래스를 만들어야 한다. 복합 기본키 역할을 할 클래스는 다음 조건을 충족해야 한다.

  • 기본 생성자가 있어야 한다.
  • Serializeable 인터페이스를 구현해야 한다.
  • equals() 메서드와 hash() 메서드를 재정의해야 한다.
  • @Embeddable 애너테이션을 붙여야 한다.

위 조건을 만족하는 클래스를 만들면 아래와 같다. 클래스 이름은 TaskEvaluationEntityId로 지었다.

@Embeddable
public class TaskEvaluationEntityId implements Serializable {
	
    @Column(name = "task_id")
    private Long taskId;
    @Column(name = "charge_team_id")
    private Long chargeTeamId;
    
    public TaskEvaluationEntityId() {
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        TaskEvaluationEntityId that = (TaskEvaluationEntityId) o;
        return Objects.equals(taskId, that.taskId) && Objects.equals(chargeTeamId,
            that.chargeTeamId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(taskId, chargeTeamId);
    }
}

 

위 클래스는 조건을 만족하기는 하지만 보일러 플레이트 코드때문에 쓸데없이 코드가 길다.

그래서 보일러 플레이트 코드를 생성해주는 Lombok을 사용했다. 

@Embeddable
@EqualsAndHashCode
@NoArgsConstructor
public class TaskEvaluationEntityId implements Serializable {

    @Column(name = "task_id")
    private Long taskId;
    @Column(name = "charge_team_id")
    private Long chargeTeamId;
}
  • @Embeddable: 해당 클래스가 엔티티에 포함될 수 있음을 JPA에게 알려주는 애너테이션. 이 애너테이션이 붙은 클래스의 필드는 Entity의 DB 테이블에 매핑된다.
  • @EqualsAndHashCode: Equals 메서드와 HashCode 메서드를 재정의해준다.
  • @NoArgsConstructor: 기본 생성자를 생성해준다.

이제 TeamEvaluation Entity 클래스를 만들어보자

@SuperBuilder
@Getter
@EqualsAndHashCode(callSuper = true)
@Entity(name = "TASK_EVALUATION")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class TaskEvaluationEntity extends BaseEntity {

    @EmbeddedId
    private TaskEvaluationEntityId id;

    @MapsId("taskId")
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "task_id")
    private TaskEntity task;

    @MapsId("chargeTeamId")
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "charge_team_id")
    private TeamEntity team;
    
    @Column(precision = 10, scale = 2)
    private double point; // 평가 점수
    
}
  • @EmbeddedId: 복합 기본 키라는 것을 알려준다. 해당 클래스에는 @Embeddable 애너테이션이 붙어있어야 한다.
  • @MapsId: EmbeddedId와 함께 사용하며 Entity 객체를 Embeddable 클래스의 필드에 연결해주는 역할을 한다.
  • @ManyToOne: TaskEvaluation는 Task와 Team에 대해 다대일(N:1) 관계를 갖고 있기 때문에 @ManyToOne을 사용했다.

 

테스트

application.yml(또는 application.properties)에 아래 설정을 추가해주고 애플리케이션을 실행해서 의도한대로 복합 기본키를 가진 테이블 생성되는지 확인해보자

  # JPA 설정 정보
 spring:
  jpa:
    hibernate:
      ddl-auto: create-drop
    defer-datasource-initialization: true
    show-sql: true  # true 설정 시, 콘솔에 JPA 쿼리 출력
    properties:
      hibernate:
        format_sql: true  # 콘솔에 표시되는 쿼리를 가독성있게 출력

 

task_evaluation 테이블을 생성하는 쿼리를 보면 primary key (charge_team_id, task_id)로 참조키를 복합 기본키로 사용하는 것을 알 수 있다.

성공!

Hibernate: 

    # team table 생성
    create table team (
       id bigint generated by default as identity,
        title varchar(50) not null,
        primary key (id)
    )
    
    # task table 생성
    create table task (
       id bigint generated by default as identity,
        project_title varchar(50),
        task_progress integer not null,
        task_state varchar(10),
        task_title varchar(300) not null,
        year varchar(4) not null,
        primary key (id)
    )
    
    # task_evaluation table 생성. 참조키를 복합키로 사용하고 있다.
    create table task_evaluation (
        charge_team_id bigint not null,
        task_id bigint not null,
        point double,
        primary key (charge_team_id, task_id)
    )
    
    alter table task_evaluation 
       add constraint FK7xru9ioepwekfal3hybwk83l0 
       foreign key (charge_team_id) 
       references team
    
    alter table task_evaluation 
       add constraint FK8vktactaa3ko3hu0yur1sbsmd 
       foreign key (task_id) 
       references task

 

참조

https://www.geeksforgeeks.org/can-a-composite-key-be-a-foreign-key-in-another-table/

 

Can a Composite Key be a Foreign Key in Another Table? - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

https://samuel-mumo.medium.com/spring-data-jpa-composite-key-mapping-example-750eb54a3d99

 

Spring Data JPA Composite Key Mapping Example

A composite key is relational databases is combination of two columns to form a primary key. This comes in handy when modelling many to…

samuel-mumo.medium.com

https://www.digitalocean.com/community/tutorials/hibernate-many-to-many-mapping-join-tables

 

Hibernate Many To Many Mapping - Join Tables | DigitalOcean

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

www.digitalocean.com



728x90