che01 님의 블로그
JPA 1:1 관계 완전 정리 본문
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단계
- 단방향으로 시작: 한쪽에서만 참조
- 필요하면 양방향: 양쪽에서 참조 가능하게
- 주인 결정: 외래키가 있는 쪽이 주인
기본 선택
- 주 테이블에 외래키 + 양방향 매핑
- 성능도 좋고 직관적이며 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 |