이 파일의 학습 목적
09b — @ManyToOne · @OneToMany · 연관관계 주인
board.getUser().getNickname()처럼 DB의 FK를 Java 객체로 접근할 수 있는 것은 @ManyToOne 연관관계 덕분이다. 09b는 DB FK ↔ Java 객체 참조를 JPA가 어떻게 연결하는지, 연관관계 주인이 누구인지를 정확하게 이해하는 것이 목표다.
이 파일에서 배울 것
- ① 연관관계 개념 — DB FK vs Java 객체 참조
- ② @ManyToOne — Board.user + @JoinColumn
- ③ @OneToMany + mappedBy — User.boards
- ④ 연관관계 주인 — FK 보유자만 DB 반영
- ⑤ 단방향 vs 양방향 — 설계 기준
프로젝트 코드 연결
- · Board.java —
@ManyToOne User user
- ·
board.getUser().getNickname() — toDto()
- ·
board.setUser(loginUser) — write()에서
- ·
deleteByUser(User user) — 회원 탈퇴
시리즈 순서:
09a 영속성 컨텍스트 →
09b ← 지금 여기
→ 09c 지연로딩·N+1
01 — 연관관계란?
DB의 FK(외래키)를 Java 객체 참조로 표현하는 방법
🔗
DB FK ↔ Java 객체 참조
Association
📝 비유 — 게시글과 작성자
DB에서는 board 테이블에 user_id 컬럼(FK)으로 연결
Java에서는 Board 객체가 User 객체를 직접 참조
→ board.getUserId() 대신 board.getUser().getNickname() 바로 접근 가능
User
id: Long
username: String
nickname: String
N : 1
←
@ManyToOne
user_id (FK)
Board
id: Long
title: String
user: User ← FK 보유
❌ FK만 저장 (불편)
public class Board {
private Long userId;
}
Long userId = board.getUserId();
User user = userRepo.findById(userId).get();
String nickname = user.getNickname();
✅ @ManyToOne 연관관계
public class Board {
@ManyToOne
@JoinColumn(name="user_id")
private User user;
}
String nickname =
board.getUser().getNickname();
02 — @ManyToOne
다대일 관계 — 가장 많이 쓰는 연관관계 · FK를 가진 쪽에 선언
🔢
Many(게시글) → One(유저)
@ManyToOne
Java — 프로젝트 Board Entity @ManyToOne 전체
@Entity
public class Board {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
private LocalDateTime createdAt;
}
Board board = boardRepository.findById(1L).get();
String nickname = board.getUser().getNickname();
Java — Comment Entity — ManyToOne 두 개
@Entity
public class Comment {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id")
private Board board;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
💡 @JoinColumn(name = "user_id")
name 속성 = DB에 실제 생성될 컬럼명
생략하면 JPA가 자동으로 user_id로 만들어주지만 — 명시적으로 쓰는 게 관례
03 — @OneToMany
일대다 관계 — 단방향보다 양방향이 실용적 · mappedBy 필수
📋
One(유저) → Many(게시글 목록)
@OneToMany
📚 비유 — 유저 입장에서 게시글 목록
유저 1명이 게시글을 여러 개 가짐
user.getBoards() → 이 유저가 쓴 글 목록 전체
→ DB에 새 컬럼은 만들지 않음 — Board 쪽 user_id가 이미 있으니까
Java — User Entity @OneToMany — 양방향 설정
@Entity
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@OneToMany(mappedBy = "user")
private List<Board> boards = new ArrayList<>();
}
User user = userRepository.findById(1L).get();
List<Board> boards = user.getBoards();
⚠️ mappedBy를 빠뜨리면?
JPA가 중간 테이블을 새로 만들어버림 → user_boards 같은 불필요한 테이블 생성
→ 반드시 mappedBy로 "FK는 저쪽이 가지고 있어" 명시해야 함
| 어노테이션 | 선언 위치 | FK 보유 | mappedBy |
| @ManyToOne | Board (많은 쪽) | ✅ Board.user_id | 불필요 |
| @OneToMany | User (하나인 쪽) | ❌ FK 없음 | 필수 — "user" 지정 |
04 — 연관관계 주인
FK를 가진 쪽이 주인 — 주인만 INSERT/UPDATE 시 DB 반영됨
👑
연관관계 주인 = FK 보유자
Owner
🗝️비유 — 열쇠를 가진 사람
집(DB)을 관리하는 열쇠(FK)는 한 명만 가질 수 있음
Board.user = 열쇠 보유자 (연관관계 주인) → INSERT/UPDATE 시 user_id 반영
User.boards = 조회 전용 (mappedBy) → 여기서 추가해도 DB에 반영 안 됨!
❌ 주인 아닌 쪽에서 설정 (DB 반영 안 됨)
User user = userRepo.findById(1L).get();
Board board = new Board();
board.setTitle("새 글");
user.getBoards().add(board);
boardRepo.save(board);
✅ 주인 쪽에서 설정 (DB 반영됨)
User user = userRepo.findById(1L).get();
Board board = new Board();
board.setTitle("새 글");
board.setUser(user);
boardRepo.save(board);
💡 양방향 설정 시 편의 메서드 권장
주인 쪽(Board)과 역방향(User.boards) 양쪽 모두 설정해두면 → 객체 그래프 탐색 시 일관성 유지
실제 DB 반영은 Board.user만 하지만, 같은 트랜잭션 내 User.boards도 동기화되어야 버그 없음
Java — 우리 프로젝트 편의 메서드 패턴
public void setUser(User user) {
this.user = user;
user.getBoards().add(this);
}
@Transactional
public void write(BoardDto dto, User loginUser) {
Board board = new Board();
board.setTitle(dto.getTitle());
board.setContent(dto.getContent());
board.setUser(loginUser);
boardRepository.save(board);
}
05 — 단방향 vs 양방향
우리 프로젝트는 어떤 방향으로 설계했나
↔️
방향 선택 기준
설계
| 구분 | 단방향 | 양방향 |
| 설명 | 한 쪽만 참조 (Board → User) | 양쪽 모두 참조 (Board ↔ User) |
| 장점 | 단순, 관리 쉬움 | user.getBoards() 편리 |
| 단점 | 역방향 탐색 불가 | mappedBy 관리, 무한루프 주의 |
| 권장 | 기본 선택 ✅ | 역방향 조회가 자주 필요할 때 |
⚠️ 양방향 연관관계 — 무한루프 주의
Board → User → boards → Board → ... JSON 직렬화 시 무한루프 발생
해결책: @JsonIgnore 또는 DTO 변환 (권장)
→ 우리 프로젝트는 DTO로 변환해서 반환 → 무한루프 방지
📌 우리 프로젝트 연관관계 구조
Board @ManyToOne User — Board가 user_id FK 보유 (단방향 주사용)
Comment @ManyToOne Board — Comment가 board_id FK 보유
Comment @ManyToOne User — Comment가 user_id FK 보유
→ API 응답은 항상 DTO로 변환 → Entity 직접 노출 안 함