Search
Duplicate
📒

[JPA 기본] 05-2. 연관관계 매핑 - 양방향 연관관계 ⭐

상태
완료
수업
JPA
주제
JPA
연관 노트
3 more properties
참고

양방향 연관관계

NOTE
단방향 → 양방향이 된다는 것의 의미는 양쪽에서 서로를 참조할 수 있다는 것이다.
기존 단방향에서는 Member에서 getTeam()을 통해 Team을 조회할 수 있지만, Team에서는 그게 불가능했다.
양방향 연관관계는 양쪽에서 모두 조회가 가능해진다.
Member Team (Member.team)
Team Member(Team.members)
양방향을 만들어주기 위해 Team객체에 Member라는 List를 추가해서 넣어준다.
@Entity public class Team{ ... @OneToMany(mappedBy = "team") private List<Member> members = new ArrayList<>(); ... }
Java
복사

양방향 연관관계의 주인

NOTE
엄밀히 말하면 객체에는 양방향 연관관계가 없다.
서로 다른 단방향 연관관계 2개를 어플리케이션 로직으로 잘 묶어서 양방향으로 보이게 하는것이다.
반면에 데이터베이스 테이블외래 키 하나로 양쪽이 서로 Join이 가능하다.
테이블은 외래키 하나만으로 양방향 연관관계를 맺는다!

객체와 테이블이 관계를 맺는 차이란?

NOTE

객체 연관관계 = 2개(단방향 2개 사용)

Member Team
Team Member
member.getTeam() team.getMembers();
Java
복사

테이블 연관관계 = 1개

Team Member
데이터베이스 테이블외래키를 사용해 Join할 수 있기 때문
SELECT * FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID SELECT * FROM TEAM T JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID
SQL
복사

양방향 매핑의 규칙

NOTE
엔티티양방향 연관관계로 설정하면 참조는 둘인데, 외래 키는 하나이다.
이럴 경우 어떤 값을 사용해서 외래키를 관리해야 하는가?
두 연관관계 중 하나를 연관관계의 주인으로 정한다!
연관관계의 주인 : 데이터베이스와 매핑되고 외래 키를 관리(등록, 수정, 삭제) 가능해짐
주인이 아님 :읽기만 가능하다.
연관관계의 주인mappedBy속성을 사용해서 정한다.
// Member @ManyToOne // Meber 입장에서는 many, Team 입장에서는 one @JoinColumn(name = "TEAM_ID") private Team team;
Java
복사
// Team @OneToMany(mappedBy = "team") private List<Member> members = new ArrayList<>();
Java
복사
주인이 아닌쪽에 mappedBy를 걸어준다. (값은 어떤 엔티티를 참조하는지를 의미)
양방향 연관관계 저장(Member가 주인인 경우)
// 팀1 저장 Team team1 = new Team("team1", "팀1"); em.persist(team1); // 회원1 저장 Member member1 = new Member("member1", "회원1"); member.setTeam(team1); // 연관관계 설정 team1 -> team1 em.persist(member1); // 회원2 저장 Member member2 = new Member("member2", "회원2"); member2.setTeam(team1); // 연관관계 설정 member2 -> team1 em.persist(member2);
Java
복사
정상적으로 처리됨

양방향 연관관계의 주인 = 외래 키가 있는 곳

NOTE
양방향 연관관계의 주인은 테이블에 외래 키가 있는 곳으로 정해야 한다!
외래키가 있는 테이블을 주인으로 정하는 이유
Member.team을 주인으로 선택하면, 자기 테이블의 외래 키를 관리하게 됨
Team.members를 주인으로 선택하면, 다른 테이블의 외래 키를 관리하게 됨
여기서는 MemberFK를 가지고 있으므로, Member가 주인이된다.
[ 참고]
데이터베이스 테이블의 다대일, 일대다 관계에서는 항상 다 쪽이 외래 키를 가진다!
다 쪽인 @ManyToOne이 항상 주인이 되므로 mappedBy를 설정할 수 없다
그래서 @ManyToOnemappedBy 속성이 없음

양방향 연관관계 주의점

NOTE
연관관계의 주인이 아닌 곳에서 값을 수정하면, 아무런 변경이 일어나지 않는다!
Member member1 = new Member("member1", "회원1"); em.persist(member1); Member member2 = new Member("member2", "회원2"); em.persist(member2); Team team1 = new Team("team1", "팀1"); team1.getMembers().add(member1); team1.getMembers().add(member2); em.persist(team1);
Java
복사
TEAM_ID에 값이 반영되지 않음!
연관관계의 주인Member.team은 아무 값도 입력 받지 않았으므로, TEAM_ID 외래 키의 값도 null이 들어간다.

순수한 객체까지 고려한 양방향 연관관계

NOTE
객체 관점에서 보면 양쪽 방향에 모두 값을 입력해주는 것이 안전하다.
JPA 관점에서는 주인쪽에서만 값을 저장하는 것은 맞지만, 양쪽 방향 모두 값을 입력하지 않으면 JPA를 사용하는 순수 객체 상태에서 심각한 문제가 생길 수 있다.

문제 발생예시 (단방향 설정)

Team team1 = new Team("team1", "팀1"); Member member1 = new Member("member1", "회원1"); Member member2 = new Member("member2", "회원2"); member1.setTeam(team1); member2.setTeam(team2); List<Member> members = team1.getMembers(); System.out.println("members.size = " + members.size()); // 기대값 : member.size = 2 // 결과: member.size = 0
Java
복사
코드에서 Member.team에만 연관관계를 설정하고 반대방향은 설정하지 않았다.
물론 commit할때 제대로 저장은 되지만, 순수 객체 상태일때가 문제다.
1차캐시에는 아직 추가된 상태가 아니기 때문

문제 발생해결 (양방향 모두 설정)

Team team1 = new Team("team1", "팀1"); Member member1 = new Member("member1", "회원1"); Member member2 = new Member("member2", "회원2"); member1.setTeam(team1); team1.getMembers().add(member1); member2.setTeam(team2); team1.getMembers().add(member2); List<Member> members = team1.getMembers(); System.out.println("members.size = " + members.size()); // 결과: member.size = 2
Java
복사

연관관계 편의 메서드

NOTE
양방향 연관관계는 결국 양쪽 모두 신경 써야하고 만약 실수한다면 양방향이 깨질 수 있다.
그래서 이를 해결하는 메소드(연관관계 편의 메서드)를 생성한다.
public class Member{ private Team team; public void setTeam(Team team) { this.team = team; team.getMembers().add(this); } // ... }
Java
복사
setTeam()함수를 정의해서, 한 쪽을 수정하면 반대쪽도 한번에 수정되도록 한다.
[ 주의사항]
양방향 매핑시 무한 루프를 조심( Ex: toString(), lombk , JSON 생성 라이브러리 )
toString 무한 루프코드