[JPA] 연관관계 매핑
[JPA] 연관관계 매핑
객체와 테이블의 연관관계 차이
테이블의 연관관계
테이블은 외래키 하나로 양방향 조회가 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- MEMBER 테이블
ID (PK)
USERNAME
TEAM_ID (FK)
-- TEAM 테이블
ID (PK)
NAME
-- 양방향 조회가 모두 가능
-- 멤버 → 팀 조회
SELECT * FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.ID;
-- 팀 → 멤버 조회
SELECT * FROM TEAM T JOIN MEMBER M ON T.ID = M.TEAM_ID;
외래키 TEAM_ID 하나만 있으면 양쪽 다 조회 가능 (양방향)
객체의 연관관계
객체는 참조(reference)를 사용해 연관관계를 갖는다.
단방향 연관관계 (Member → Team만 탐색 가능)
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
@Entity
public class Member {
@Id
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team; // Member에서 Team 참조
}
@Entity
public class Team {
@Id
private Long id;
private String name;
// Team에서 Member 참조 없음!
}
// Member → Team 조회 가능
Member member = em.find(Member.class, 1L);
Team team = member.getTeam(); // ✅ 가능
// Team → Member 조회 불가능
Team team = em.find(Team.class, 1L);
List<Member> members = team.getMembers(); // ❌ 불가능! 참조가 없음
- 이 상태는 단방향 Member → Team만 가능
양방향 연관관계
양방향 연관관계를 만들려면 단방향 연관관계 2개를 만들어야 한다.
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
@Entity
public class Member {
@Id
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team; // 1. Member → Team 참조
}
@Entity
public class Team {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>(); // 2. Team → Member 참조
}
// 양방향 조회 모두 가능
Member member = em.find(Member.class, 1L);
Team team = member.getTeam(); // Member → Team
Team team = em.find(Team.class, 1L);
List<Member> members = team.getMembers(); // Team → Member
그럼 양쪽에서 참조를 가지고 있으면 누가 외래키를 관리해야 할까?
연관관계의 주인 (mappedBy)
- 연관관계 주인만이 외래키를 관리(등록/수정) 즉 외래키가 있는 곳을 주인으로 정하자
- 주인이 아닌 쪽은 읽기만 가능하다.
- 주인은
mappedBy속성을 사용하지 않는다 - 주인이 아닌쪽에서
mappedBy로 주인을 지정한다.
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
public class Member {
@ManyToOne
@JoinColumn(name = "team_id")
private Team team; // 연관관계의 주인 (외래키 관리)
}
@Entity
public class Team {
@OneToMany(mappedBy = "team") // 주인 아님 (읽기만 가능)
private List<Member> members = new ArrayList<>();
}
왜 Member가 주인일까?
- MEMBER 테이블에 TEAM_ID (FK) 가 있기 때문에
- N:1 관계에서 항상 N쪽이 외래키를 가진다.
- 따라서
@ManyToOne쪽이 연관관계의 주인
양방향 매핑 시 주의사항
순수 객체 상태를 고려해 항상 양쪽에 값을 설정하자
잘못된 예시 →
1
2
3
4
5
6
7
8
9
10
//주인 쪽만 설정
Team team = new Team();
em.persist(team);
Member member = new Member();
member.setTeam(team); // 주인 쪽만 설정
em.persist(member);
// Team에서 조회하면?
team.getMembers().size(); // 0 (양쪽 설정 안해서 비어있음)
옳게된 예시 →
1
2
3
4
5
6
7
8
//양쪽 모두 설정
Team team = new Team();
em.persist(team);
Member member = new Member();
member.setTeam(team); // 주인 쪽 설정
team.getMembers().add(member); // 반대편도 설정
em.persist(member);
하나하나 신경쓰기 귀찮으니 연관관계 편의 메서드를 작성하자
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
public class Member {
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
// 연관관계 편의 메서드
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this); // 양쪽 모두 설정
}
}
1
2
3
// 사용
Member member = new Member();
member.changeTeam(team); // 한 번에 양쪽 설정!
다양한 연관관계 매핑
N:1 (@ManyToOne)
1
2
3
4
5
6
@Entity
public class Member {
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
- 외래키가 있는 쪽이 연관관계 주인
- 가장 많이 사용한다.
- 양방향 시 Team에
@OneToMany(mappedBy = "team")추가
1:N (@OneToMany)
1
2
3
4
5
6
@Entity
public class Team {
@OneToMany
@JoinColumn(name = "team_id") // 반대 테이블의 외래키 관리
private List<Member> members = new ArrayList<>();
}
- 1 이 연관관계의 주인
- 다른 테이블의 외래키를 관리해 복잡하다.
- 실무에서는 N:1 양방향을 사용
1:1 (@OneToOne)
1
2
3
4
5
6
@Entity
public class Member {
@OneToOne
@JoinColumn(name = "locker_id")
private Locker locker;
}
- 주 테이블이나 대상 테이블 중 외래키 선택 가능
N:M (@ManyToMany)
1
2
3
4
5
6
@Entity
public class Member {
@ManyToMany
@JoinTable(name = "member_product")
private List<Product> products = new ArrayList<>();
}
- 실무에서 사용하지 않음
- 중간 테이블이 숨겨져 있어 예상치 못한 쿼리 발생
- 중간 테이블에 추가 정보를 넣을 수 없다.
해결 → 중간 엔티티를 만들어 N:1, 1:N 으로 풀자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity
public class MemberProduct {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne
@JoinColumn(name = "product_id")
private Product product;
private int orderAmount; // 추가 정보 가능
}
실전 팁
먼저 단방향 매핑으로 설계하자
1
2
3
4
5
6
7
// 1단계: 단방향 매핑으로 설계 완료
@Entity
public class Member {
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
- 단방향 매핑만으로 이미 연관관계 매핑은 완료된다.
양방향은 필요할 때 추가하면 된다.
- JPQL 역방향 탐색이 필요한 경우
-
객체 그래프 탐색이 필요한 경우
1 2 3 4 5 6
// 2단계: 필요하면 양방향 추가 @Entity public class Team { @OneToMany(mappedBy = "team") private List<Member> members = new ArrayList<>(); }
이유 →
- 테이블에 영향을 주지 않는다 (테이블은 외래키만으로 충분)
- 단방향 매핑으로 이미 연관관계 설정 완료
- 양방향은 객체 그래프 탐색 편의를 위한 것
- 양방향 설계는 객체 입장에서 고민만 많아진다…
단방향 매핑만으로 이미 연관관계 매핑은 완료된다. 양방향 설계는 객체 입장에서 고민만 많아지니 단방향으로 설계를 완료한 후 양방향은 필요한 경우 추가하자!
This post is licensed under
CC BY 4.0
by the author.