FK의 위치 / 연관관계 주인, 방향, 종속성

양성준·2025년 4월 14일

스프링

목록 보기
30/49

FK(외래키)의 위치

FK의 위치는 DB 설계 시에 매우 중요한 결정 사항이며, JPA 같은 ORM 환경에서는 객체 탐색, 연관관계 주인, 생명주기 등과도 깊게 된다.

1. "종속 관계" 기준으로 FK 위치 정하기

  • 종속적인 테이블이 FK를 가지는 것이 자연스럽다.
  • FK가 종속적인 쪽에 있어야, 데이터가 추가되어도 메인테이블을 수정하지 않아도됨. (확장성면에서 유리)
    • User 테이블에 FK가 있다면, UserStatus가 추가되면, User 테이블을 수정해야함
  • User : UserStatus
    • UserStatus는 User 없이는 존재할 수 없음 → User에 종속됨
    • 따라서 FK는 user_status.user_id
  • Channel : Message
    • Message는 Channel 없이는 존재 불가 → FK: message.channel_id
  • 종속 관계에서는 ON DELETE CASCADE 설정을 활용하기 쉬움
  • 무결성과 정합성을 보장하기 좋음

2. "참조 관계"라면 참조하는 쪽이 FK를 갖는다

  • 다른 테이블에서 자유롭게 참조되는 독립적인 엔티티는, 이를 참조하는 쪽이 FK를 가진다.
  • User : BinaryContent
    • 사용자 프로필 이미지로 BinaryContent를 사용하는 경우
    • BinaryContent는 User 외에도 Message 등 다양한 곳에서 쓰일 수 있음 → 독립적 리소스
    • 따라서 FK는 User.profile_id
  • 참조 관계는 재사용성과 독립성이 중요
  • 참조하는 쪽에서 FK를 가짐으로써 결합도를 낮출 수 있음

3. 1:N 관계에서는 항상 N쪽에 FK를 둔다

  • 관계형 DB는 리스트(List)를 컬럼으로 저장할 수 없기 때문에, FK는 반드시 N쪽에 위치해야 한다.
  • User : Message
    • 하나의 User가 여러 Message를 작성 → FK: message.author_id
  • Channel : ReadStatus
    • 하나의 Channel에 여러 ReadStatus → FK: read_status.channel_id
  • JPA에서도 연관관계 주인은 FK를 가진 쪽이 되어야 하므로 자연스럽게 N쪽이 주인 역할을 맡는다.

정리

관계 유형FK를 두는 위치비고
종속 관계종속된 테이블생명주기 의존, CASCADE 설정 용이
참조 관계참조하는 테이블독립성 보장, 결합도 낮춤
1:N 관계N쪽 테이블구조적으로 필수

=> FK는 어디에 있어도 상관없지만, 성능상의 이점이나 편의성을 위해 두면 좋은 곳이 존재한다!


DB 설계 <-> JPA 엔티티 설계

DB 설계는 무결성과 성능 중심

  • FK는 종속성과 삭제 연계 등 정합성 중심으로 배치
  • 단방향 관계만 존재

JPA는 탐색성과 사용성 중심

  • User.getStatus()처럼 객체 탐색이 중요
  • 양방향 매핑이 가능, 즉 필드가 선언된 방향으로 탐색할 수 있음

=> 그래서, FK는 종속성(UserStatus는 User에 종속)에 따라 user_status.user_id에 있지만, JPA에서는 User 엔티티에 UserStatus 필드를 선언해 user.getUserStatus()로 탐색하게끔 구현 가능! (UserStatus에서 User를 탐색하는 경우가 많다면, UserStatus에도 User를 추가하여 양방향 매핑 가능)

User -> UserStatus를 탐색하기 때문에 User에서도 UserStatus를 가지고,
JPA에서는 FK를 가진 쪽을 연관관계의 주인으로 하는것을 권장하기 때문에 UserStatus도 User를 가져서 연관관계를 맺음 (탐색하지 않더라도)


중간 연결 테이블

  • 중간 테이블은 N:M 관계를 1:N / M:1로 풀어낼 때 사용된다.
  • 예: Message : BinaryContent
    • 하나의 Message가 여러 BinaryContent를 첨부 가능
    • 하나의 BinaryContent가 여러 Message에 첨부될 수 있음
    • N:M 관계이므로 중간 테이블(message_attachments) 필요

→ 반대로, 1:1이나 1:N 관계에서는 중간 테이블 없이 FK로 직접 연결하는 것이 일반적


연관관계의 "주인" <-> "탐색(연결) 방향"

개념연관관계 주인탐색 방향
의미FK를 실제로 관리하는 쪽어느 쪽에서 참조할 수 있는지 (JPA 필드 선언 기준)
결정 기준@JoinColumn이 선언된 쪽필드가 선언된 쪽이 탐색 가능

예시:

@Entity
class ReadStatus {
    @ManyToOne
    @JoinColumn(name = "user_id") //연관관계의 주인 (FK 관리)
    private User user;
}

@Entity
class User {
    // 탐색은 불가능 (단방향)
}
→ 탐색은 ReadStatusUser만 가능, 연관관계 주인도 ReadStatus
  • 연관 관계의 주인 필드는 수정을 하면 INSERT나 UPDATE 쿼리가 날아가지만,
    주인이 아닌 경우에는 아무런 쿼리가 날아가지 않는다.
    • DB에는 양방향이 없으므로, FK를 가진 주인 엔티티에서 연관관계 엔티티 필드를 수정
      -> FK가 수정되는 쿼리가 날아감 (INSERT, UPDATE)
    • 하지만, 주인이 아닌 쪽에서 수정을 하면, DB에는 해당하는 FK 필드가 없기 때문에 반영이 되지 않는다.
@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "team_id") // FK 소유자 = 연관관계의 주인
    private Team team;
}

@Entity
public class Team {
    @OneToMany(mappedBy = "team") // 연관관계의 주인이 아님
    private List<Member> members = new ArrayList<>();
}

Team team = new Team();
Member member = new Member();

team.getMembers().add(member); // 비주인만 수정
em.persist(member);            // member.team은 null
  • 연관관계의 주인은 Member (FK가 존재), 주인이 아닌 Team에서 아무리 getMembers를 수정해봤자,
    Team에는 Member FK가 존재하지 않기 때문에 쿼리가 날아가지 않음
  • 그래서, Team에서 Member를 add할 때 자연스레 연관관계 편의 메서드를 추가하여 Member 테이블에 쿼리를 날리도록 해야함
public void addMember(Member member) {
    members.add(member);          // team → member
    member.setTeam(this);         // member → team
}
  • 자연스레 member가 set되면서, update 쿼리가 날아가 DB에 반영된다.
profile
백엔드 개발자

0개의 댓글