TIL - #30 JPA심화! - 복합키의 사용

Quann·2023년 2월 2일
0

00. 개요

JPA의 심화에 대해 공부하며, 다양한 개념적 공부를 진행했다.

Raw JPA를 통한 테이블 매핑과 애너테이션(@Entity, @Table, @Id 등)

필드 타입 매핑을 위한 애너테이션(@Column, @Embedded, @Enumerated 등)

연관관계 매핑 기능(@OneToOne, @OneToMany 등)

등등 다양한 기술이 존재하는데, 이에 대한 부분은 이미 정립된 글이 많이 존재한다.

하지만, 이 외에도 더욱 다양한 기술이 있다는 것을 깨달았기 때문에,

그 중 재미있게 코드를 친 내용 중 하나인 복합키의 사용을 적고자 한다.


01. 복합키

JPA에서 제공하는 연관관계 매핑 기능의 연장선상에서 볼 수 있는 기능이다.

들어가기에 앞서, 간단히 설명하면

JPA를 통해 복합키를 구현하는 경우의 경우,

단일키의 경우, PK가 1개 있는 경우를 말하며, 해당 키 값이 단일키가 된다.
=> 해당 값은 데이터를 구분할 수 있는 식별자가 된다.

복합키의 경우, PK 없이 FK 2개 이상으로 있는 경우를 말하며, 해당 키의 묶음이 복합키가 된다.
=> 해당 값은 데이터를 구분할 수 있는 식별자가 된다.


02. JPA와 복합키

이러한 복합키를 사용하기 위해 JPA에서는 두 가지의 기능을 제공한다.

02.01. @IdClass

복합키를 사용할 엔티티 위에 @IdClass(식별자 클래스) 애너테이션을 붙여주어 동작하는 방식이다.

@Entity
@IdClass(UserChannelId.class) // 어떤 
public class UserChannel {
  @Id
  @ManyToOne
  @JoinColumn(name = "user_id")
  User user;

  @Id
  @ManyToOne
  @JoinColumn(name = "channel_id")
  Channel channel;
}
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserChannelId implements Serializable {
  private Long user; // UserChannel 의 user 필드명과 동일해야 한다.
  private Long channel; // UserChannel 의 channel 필드명과 동일해야한다.

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    UserChannelId userChannelId = (UserChannelId) o;
    return Objects.equals(getUser(), userChannelId.getUser()) && Objects.equals(getChannel(), userChannelId.getChannel());
  }

  @Override
  public int hashCode() {
    return Objects.hash(getUser(), getChannel());
  }
}

UserChannel Entity는 복합키를 가지는데,
해당 Id는 UserChannelId 클래스를 통해 복합키로 생성된다.
따라서, UserChannelId의 경우 2개의 Long 값을 가지는데,
이 두개의 Long 값이 UserChannel 클래스에서 @Id 로 선언한 2개 필드에 대한 id 값으로 매핑되는 것이다.

이 때, EqualsAndHashCode를 정의해,
비교 판단 기준을 user_idchannel_id로 두어,
복합키를 바탕으로 식별할 수 있는 특성을 구현한다.

02.02. @EmbeddedId

두 번째 방법으로는, @EmbeddedId 애너테이션을 통한 복합키 생성이다.

@Entity
public class UserChannel {
  @EmbeddedId
  private UserChannelId userChannelId;

  @ManyToOne
  @MapsId("user_id")
  User user;

  @ManyToOne
  @MapsId("channel_id")
  Channel channel;

}
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Embeddable
public class UserChannelId implements Serializable {

  @Column(name = "user_id")
  private Long userId;

  @Column(name = "channel_id")
  private Long channelId;

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    UserChannelId userChannelId = (UserChannelId) o;
    return Objects.equals(getUser(), userChannelId.getUser()) && Objects.equals(getChannel(), userChannelId.getChannel());
  }

  @Override
  public int hashCode() {
    return Objects.hash(getUser(), getChannel());
  }
}

@IdClass와 동일하게 UserChannelId가 복합키의 바탕이 되는 것은 똑같지만,

UserChannelId 클래스에 @Embeddable 애너테이션을 통해 Entity 객체 내에 임베드 될 수 있도록 하고,
UserChannel 객체는 @Embedded를 통해 UserChannelId 객체를 갖고 있게 된다.
또한, 객체로 갖고있는 User와 Channel에 대해 @MapsId 애너테이션을 통해 어떤 키값에 대해 대응시킬지 명시해주어

객체 생성시, UserChannelId 기반의 복합키가 생성되는 방식이다.


03. 정리

이에 대해 공부하면서,

기본키, 외래키, 복합키, 후보키 등의 개념에 대해 배웠고

JPA에서 복합키는 어떻게 구현하는지에 대해 학습할 수 있었다.

사실, 개념을 아는 것 보다는 언제 기본키를 사용하고, 언제 복합키를 사용하는지 아는 것이 중요하다고 생각한다.

복합키 생성 법을 알아도, 언제 복합키를 적용해야하는지 모르면 말짱 도루묵이기 때문이다.

실습을 진행하면서, 다른 필드가 추가될 경우가 없는 경우에 한해서,
한 객체가 다른 두 객체의 PK값을 FK 값으로 들고있는 경우에 해당 복합키 기능을 적용하면
별도의 PK 값을 두지 않고 FK 2개를 복합키로 사용해 자원의 절약이 가능할 것이라 생각하긴 했는데,
이에 대한 고민은 좀더 진행해봐야할 것 같다.


04. 오늘의 한 문단

닭 잡는데 소 잡는 칼 쓰지 말자

profile
코드 중심보다는 느낀점과 생각, 흐름, 가치관을 중심으로 업로드합니다!

0개의 댓글