Notice
Recent Posts
Recent Comments
Link
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

che01 님의 블로그

JPA 1:1 관계 완전 정리 본문

Spring

JPA 1:1 관계 완전 정리

che01 2025. 6. 9. 11:10

1:1 관계란 무엇인가?

현실 예시로 이해하기

  • 사람 1명 ↔ 주민등록증 1개
  • 회사 1개 ↔ 대표자 1명
  • 학생 1명 ↔ 학생증 1개

이처럼 하나의 A가 하나의 B와만 연결되는 관계를 1:1 관계라고 합니다.

사용자와 프로필 예시로 알아보기

실제 서비스에서 많이 사용하는 사용자(User)와 프로필(Profile) 관계로 설명하겠습니다.

상황 설정

  • 한 명의 사용자는 하나의 프로필만 가질 수 있다
  • 하나의 프로필은 한 명의 사용자에게만 속한다

1단계: 단방향 관계 (한쪽에서만 참조)

방법 1: User가 Profile을 참조 (추천)

테이블 모습

User 테이블                Profile 테이블
┌─────────────────────┐    ┌──────────────────┐
│ id (PK)        │ 1  │    │ id (PK)     │ 1 │
│ username       │kim │    │ bio         │..│
│ profile_id (FK)│ 1 ────→ │ nickname    │..│
└─────────────────────┘    │ created_at  │..│
                           └──────────────────┘

코드로 표현하면

// User가 Profile을 참조
@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;
    
    private String username;
    
    // User가 Profile을 가리킴 (단방향)
    @OneToOne
    @JoinColumn(name = "profile_id")
    private Profile profile;
}

// Profile은 User를 모름
@Entity
public class Profile {
    @Id
    @GeneratedValue
    private Long id;
    
    private String bio;
    private String nickname;
    
    // User 참조 없음 (단방향이므로)
}

사용 방법

// User에서 Profile 조회 가능
User user = userRepository.findById(1L);
Profile profile = user.getProfile(); // 가능!

// Profile에서 User 조회 불가능
Profile profile = profileRepository.findById(1L);
User user = profile.getUser(); // 불가능! (필드가 없음)

방법 2: Profile이 User를 참조 (비추천)

테이블 모습

User 테이블            Profile 테이블
┌──────────────────┐   ┌─────────────────────┐
│ id (PK)     │ 1  │   │ id (PK)        │ 1  │
│ username    │kim │   │ bio            │..  │
└──────────────────┘   │ user_id (FK)   │ 1 ←┘
                       └─────────────────────┘

문제점: JPA에서 완전히 지원하지 않음

2단계: 양방향 관계 (양쪽에서 서로 참조)

방법 1: User 테이블에 외래키 (추천)

테이블 모습

User 테이블                Profile 테이블
┌─────────────────────┐    ┌──────────────────┐
│ id (PK)        │ 1  │    │ id (PK)     │ 1 │
│ username       │kim │    │ bio         │..│
│ profile_id (FK)│ 1 ────→ │ nickname    │..│
└─────────────────────┘    └──────────────────┘
                                      ↑
                           서로 참조 가능 (양방향)

코드로 표현하면

// User가 연관관계의 주인 (외래키 관리)
@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;
    
    private String username;
    
    // 주인: 외래키를 직접 관리
    @OneToOne
    @JoinColumn(name = "profile_id")
    private Profile profile;
}

// Profile은 읽기 전용
@Entity
public class Profile {
    @Id
    @GeneratedValue
    private Long id;
    
    private String bio;
    private String nickname;
    
    // 읽기 전용: mappedBy 사용
    @OneToOne(mappedBy = "profile")
    private User user;
}

사용 방법

// 양쪽에서 모두 조회 가능
User user = userRepository.findById(1L);
Profile profile = user.getProfile(); // 가능!

Profile profile = profileRepository.findById(1L);
User user = profile.getUser(); // 가능!

방법 2: Profile 테이블에 외래키 (성능 문제 있음)

테이블 모습

User 테이블            Profile 테이블
┌──────────────────┐   ┌─────────────────────┐
│ id (PK)     │ 1  │   │ id (PK)        │ 1  │
│ username    │kim │   │ bio            │..  │
└──────────────────┘   │ user_id (FK)   │ 1 ←┘
        ↑              └─────────────────────┘
서로 참조 가능하지만 성능 문제 있음

코드로 표현하면

// User는 읽기 전용
@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;
    
    private String username;
    
    // 읽기 전용: mappedBy 사용
    @OneToOne(mappedBy = "user")
    private Profile profile;
}

// Profile이 연관관계의 주인
@Entity
public class Profile {
    @Id
    @GeneratedValue
    private Long id;
    
    private String bio;
    private String nickname;
    
    // 주인: 외래키를 직접 관리
    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;
}

핵심 개념 이해하기

연관관계의 주인이란?

쉽게 설명하면

  • "외래키를 실제로 관리하는 쪽"
  • 데이터베이스의 외래키 값을 변경할 수 있는 권한을 가진 쪽

예시로 이해하기

// User가 주인인 경우
User user = new User();
Profile profile = new Profile();

// 이렇게 설정해야 DB에 반영됨
user.setProfile(profile); // 주인쪽에서 설정

// Profile쪽에서만 설정하면 DB에 반영 안됨
profile.setUser(user); // 읽기 전용이라 무시됨

mappedBy의 의미

mappedBy = "profile"의 뜻

  • "나는 연관관계의 주인이 아니야"
  • "상대방 클래스의 'profile' 필드가 주인이야"
  • "그 필드를 보고 매핑해줘"

어떤 방법을 선택해야 할까?

주 테이블에 외래키 (User 테이블) - 추천

장점

  • 성능이 좋다: User만 조회해도 Profile 존재 여부를 알 수 있음
  • 지연 로딩이 정상 작동: 필요할 때만 Profile을 가져옴
  • 직관적: User가 Profile을 "소유한다"는 느낌

단점

  • Profile이 없는 User가 있을 수 있음 (null 허용)

대상 테이블에 외래키 (Profile 테이블) - 비추천

장점

  • 데이터 무결성: 모든 Profile은 반드시 User를 가져야 함
  • 1:N 변경시 유리: 나중에 "한 User가 여러 Profile을 가질 수 있다"로 변경하기 쉬움

단점

  • 성능 문제: 지연 로딩이 제대로 작동하지 않음
  • 복잡함: 설정이 헷갈리기 쉬움

실무에서는 어떻게?

99%의 경우: 주 테이블에 외래키

// 일반적인 패턴
@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;
    
    private String username;
    
    @OneToOne
    @JoinColumn(name = "profile_id")
    private Profile profile;
}

@Entity
public class Profile {
    @Id
    @GeneratedValue
    private Long id;
    
    private String bio;
    
    @OneToOne(mappedBy = "profile")
    private User user;
}

특별한 경우에만: 대상 테이블에 외래키

  • 데이터 정합성이 매우 중요한 시스템
  • 나중에 1:N 관계로 변경될 가능성이 높은 경우
  • 성능보다 데이터 무결성을 우선시하는 경우

요약

1:1 관계 설정 3단계

  1. 단방향으로 시작: 한쪽에서만 참조
  2. 필요하면 양방향: 양쪽에서 참조 가능하게
  3. 주인 결정: 외래키가 있는 쪽이 주인

기본 선택

  • 주 테이블에 외래키 + 양방향 매핑
  • 성능도 좋고 직관적이며 JPA가 잘 지원함

핵심 기억사항

  • 외래키가 있는 쪽이 연관관계의 주인
  • mappedBy는 "나는 주인이 아니야"라는 뜻
  • 주 테이블에 외래키를 두는 것이 일반적으로 좋음

'Spring' 카테고리의 다른 글

JPA 상속관계 매핑 전략 완전 정리  (2) 2025.06.09
JPA N:M 관계 완전 정리  (0) 2025.06.09
JPA 1:N 관계 완전 정리  (0) 2025.06.09
Spring 데이터 변환 메커니즘 완벽 정리  (0) 2025.06.06
Spring Formatter란 ?  (0) 2025.06.06