JPA에서 가장 기본이 되는 연관관계 매핑 중 다대일과 일대다 매핑에 대해 알아보자.
먼저 아래 두 개의 클래스를 알아보자.
Member
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private String id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID") // 외래 키 설정
private Team team;
public void setTeam(Team team) {
this.team = team;
}
// Getter, Setter ...
}
Team
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
@OneToMany(mappedBy = "team") // 주인이 아님을 표시
private List<Member> members = new ArrayList<>();
// Getter, Setter ...
}
코드를 보면 Member에 @JoinColumn을 쓰고 Team에 mappedBy를 쓴 것을 알 수 있다.
다(N)쪽: Member
- 실제로 데이터베이스에서 TEAM_ID라는 외래 키를 갖고 있다.
- 따라서 @JoinColumn(name="TEAM_ID")를 붙여서 나는 연관관계의 주인이고 이 필드를 통해 Team과 연관되어 있다 라고 JPA에 알려준다.
일(1)쪽: Team
- 외래 키를 갖고 있지 않다.
- 대신 OneToMany(MappedBy = "team")을 붙여서 연관관계의 주인은 Member.team이다. 나는 그냥 거울이다 라고 알려준다.
그럼 연관관계의 주인이란 무슨 뜻일까?
연관관계의 주인 = 외래 키를 관리하는 쪽 = 데이터베이스에 변화를 줄 수 있는 쪽
양방향 매핑에선 반드시 한 쪽이 주인이고 다른 쪽은 mappedBy를 통해 반대편임을 명시해야 한다.
여기선 Member가 주인이고 Team은 주인이 아니다.
왜 Team은 List<Member>로 갖고 있을까?
정답은 한 팀에 여러 명의 멤버가 속하니까. 이게 끝이다,
1 : N (Team : Members) 관계를 표현하기 위해서 컬렉션(리스트)으로 멤버들을 관리한다.
실제 사용할 시 team.getMembers()로 소속된 멤버 조회가 가능하다.
그럼 지연 로딩과 즉시 로딩은 여기서 어디에 써야 할까?
JPA는 연관된 엔티티를 가져올 때 LAZY 방식이나 EAGER 방식 중 하나로 로딩한다.
각 단계를 보면서 로딩 전략을 어떻게 하면 좋을 지 알아보자.
Member → Team (@ManyToOne)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
Team → Members (@OneToMany)
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private List<Member> members = new ArrayList<>();
- Team을 조회할 때 멤버가 엄청 많을 수도 있다.
- 이걸 EAGER로 하면? 쿼리 부담이 커져서 성능 저하의 가능성이 있다.
결론은 가능한 한 모든 연관관계를 LAZY로 설정하는 것이 좋다.
중요한 연관관계 편의 메서드에 대해서도 알아보자.
양방향 연관관계에선 양쪽을 함께 세팅해줘야 데이터가 일치한다.
Member member = new Member();
Team team = new Team();
member.setTeam(team); // 팀에 멤버가 추가되지는 않음!
member.setTeam(team)을 보면 단순히 Member 객체의 team 필드에 Team 객체를 참조로 넣어준 것이다.
즉, 단방향 참조만 설정한 상태이다.
Team 객체를 보면 이 필드는 단순히 컬렉션이라는 것을 알 수 있다.
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
add()를 하지 않으면 아무것도 모르는 상태다.
내가 member.setTeam(team)을 하더라도 team.getMembers()에는 아무런 변화가 없다.
members 리스트에 add(member)를 해줘야 인식이 된다.
이 상태로 영속성 컨텍스트에 저장하면?
둘다 em.persist를 하면 DB엔 member가 team_id를 참조하고 있지만 team.getMembers()는 여전히 비어있는 상태이다.
객체 간 참조는 불일치하고 DB의 값과 객체 그래프가 다르게 돌아가는 상황이 된다.
그래서 필요한 것이 연관관계 편의 메서드이다.
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this); // 양방향 모두 연결!
}
이렇게 하면 member는 team을 가리키고 team도 members에 member를 포함하게 된다.
'Backend > JPA' 카테고리의 다른 글
[JPA] 간단한 문제 해결(회원가입,조회) (0) | 2025.04.10 |
---|---|
[JPA] 영속성 컨텍스트부터 엔티티 생명주기까지 확실히 알자! (0) | 2025.04.07 |