연관관계
JPA에 의해 관리되는 Entity들의 실제 DB Table에서, Join Query를 통해 연관관계가 필요한 Table이 있다.
Foreign Key를 통해서 Join을 한 다음에 연관관계를 맺어주는데, (RDB에서는)
JPA에서는 이런 관계를 어떻게 맺어줄 수 있을까?
실제 테이블에 맞춰보기
Member는 Team에 소속될 수 있다
현재 자신이 속한 Team을 DB에 저장하기 위해, Team의 PK인 TEAM_ID를 자신의 FK로 가지고 있다
Table 구조상 문제될건 없지만, 객체 구조가 저렇게 된다면
JPA를 쓰는 목적에 어긋나게 된다
우리는 처음에 객체지향적으로 DB를 사용하고 싶어서 JPA를 쓴 것이었다
Member가 속한 Team을 찾고 싶다면 다음과 같이 코드를 작성할 것이다
Member findMember = em.find(Member.class, member.getId());
var teamId = findMember.getTeamId();
Team findTeam = em.find(Team.class, teamId);
객체지향의 최대 장점인 레퍼런스를 활용하지 않고, id로 다시 한번 Team을 찾아야 한다
우리가 원하는 코드는 위에가 아니라 아래와 같다
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam();
이렇게 된다면 레퍼런스로 바로 자신이 속한 Team 객체를 가져올 수 있으며 ERD는 다음과 같이 될 것이다
객체지향스럽게 바꿔보기
Member가 자신이 속한 Team을 레퍼런스로 가지고, 이를 JPA로 처리하려면 다음과 같은 작업이 필요하다
- 방향과 다중성 설정
- 해당 객체를 Join으로 가져올 Column 이름
@Entity
public class Member {
//...
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
//...
}
객체간의 연관관계는 크게 4가지로 이루어져있다.
- 1:1 (일대일)
- 1:N (일대다)
- N:1 (다대일)
- N:M (다대다)
Member 입장에서, Team은 단 하나만 존재해야 하고
반대로 Team 입장에서는 여러개의 Member가 자신을 참조하고 있을 수 있으므로
@ManyToOne, 다대일 관계를 설정해주었다.
그리고 @JoinColumn을 통해 자신이 참조로 가지고 올 Team 객체를 가져올
Join Query에 필요한 Column name을 지정해주었다
정리하자면, Member는 Team을 레퍼런스로 가지고 있어야 하므로
@ManyToOne으로 연관관계를 설정해 주어 JPA에게 알려주었고,
Team은 현재 ERD 상으로는 Member를 레퍼런스로 가지고 있을 필요가 없으므로
아무런 작업을 해주지 않았다.
이런식으로 연관관계 설정을 해주면 id로 찾아오는 DB스러운 방식보다,
참조로 가져오는 객체지향스러운 방식으로 JPA를 활용할 수 있다.
Team도 Member를 가져야 한다면?
만약 Member와 Team이 서로를 레퍼런스로 가지고 있어야 한다면(양방향)
어떻게 해주어야 할까?
우선 ERD 상에서 테이블간의 연관관계는 전혀 바뀐게 없음을 확인할 수 있다.
오직 객체 연관관계에서만, Team 객체에서 List가 추가되었다
왜 그럴까?
애초에 DB 테이블간의 방향성은 존재하지 않는다.
다른 Table이 필요하다면 FK를 이용해서 찾아오면 되기때문이다
즉, 양방향이라는 말은 존재하지 않고 무향에 가깝다고 이해하면 된다
또 객체의 양방향이라는것은 사실 단방향이 2개 있는것과 완벽히 동일하다
이렇게 방향성이 없는 DB Table을 양방향 객체로 가져오기 위해서는 추가 작업이 필요하다
- 방향과 다중성 설정
- 연관관계의 주인 설정(mapped by)
@Entity
public class Team {
//...
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
//...
}
ERD는 다음과 같을 것이다
위의 코드에서 mappedBy라는 키워드가 등장하였는데, 얘는 무슨 뜻일까?
다음과 같은 상황을 생각해보자
MemberA, B와 TeamA, B가 있다.
MemberA의 팀은 원래 TeamA였는데,
memberA.setTeam(teamB);
TeamB로 바꾸게 된다면 DB입장에서는 MEMBER Table의 TEAM_ID(FK)를 Update해주어야 할 것 같다
그 다음에 TeamB에다 MemberA가 들어왔음을 반영해줄 것이다.
teamB.getMembers().add(memberA);
DB입장에서는 또 MEMBER Table의 TEAM_ID(FK)를 Update해주어야 할 것 같다
기준이 모호해졌다
Member <-> Team 의 관계를 맺어주는것은 TEAM_ID라는 FK인데,
어느 객체의 정보에 맞춰서 Update를 해줘야 할지 모르는것이다.
만약 mappedBy를 사용하지 않는다고 가정했을때,
위와 같은 상황에서는 아마도 똑같이 Update Query가 두 번 나갈 것이다.
하지만 프로그래머의 실수로 둘 사이의 sync가 안맞는다면?
다시 말해 참조를 까먹고 잘못 설정해 주었다면?
어쩔줄을 모를것이다.
따라서 JPA는 자체적인 룰을 두어 양방향 연관관계에서는 FK를 관리하는 연관관계의 주인을 설정하도록 한다
주인만 Read / Write의 권한을 가지며
주인이 아닌쪽은 Read만 할 수 있다.
mappedBy가 붙어있는 쪽의 반대편이 주인이 되며, 위 코드에서는
Member가 주인이고 Team이 'Member가 관리하는 Team 객체에 Mapped'인 상황이다.
다시 말해 Team의 List에 Member를 추가하는것은 DB에 어떤 변화도 일으키지 않는다는 점이다(Read Only이므로)
team.getMembers().add(member); // 연관관계의 주인이 아니므로 DB에 반영 x
member.setTeam(team); // 연관관계의 주인이므로 DB에 반영 o
// 연관관계를 맺어주는 FK가 TEAM_ID이므로
// MEMBER TABLE의 TEAM_ID를 Update 해준다
가장 많이 하는 실수여서 조심해야 한다
양방향 연관관계 편의 메소드
위에서 봤던것처럼 연관관계의 주인인 객체의 값을 수정해야 DB가 제대로 Update 된다.
하지만 이는 DB만의 관점이고, 원래 우리가 살던 객체지향의 관점은
당연히 둘 다 수정해주어야 한다
team.getMembers().add(member);
member.setTeam(team);
DB에 Update를 일으키는건 연관관계의 주인인 Member이지만,
DB에서가 아닌 객체세상의 객체를 제대로 Update하려면 Team에도 정보를 반영해주어야한다
이를 좀 편하게 하기 위해서 다음과 같은 메소드를 많이 작성한다고 한다
@Entity
public class Member {
void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
따라서 changeTeam 함수만 호출해주게 되면
객체끼리의 로직도 Update 해주고, DB에서도 MEMBER Table의 TEAM_ID FK를 Update 해준다
member.changeTeam(team);
양방향 매핑은 단순히 단방향 매핑에서 역방향 탐색이 가능하도록 만들어주는것이다.
나중에 JPQL에서 역방향으로 탐색할 일이 많다고 한다
정리
DB Table과 객체지향 언어간의 패러다임 불일치 문제를 JPA는 연관관계 Mapping을 통해 해결한다
자주 사용되는 연관관계로는 ManyToOne, OneToOne이며
OneToMany는 Entity가 관리하는 FK가 자신 테이블이 아닌 상대 테이블에 있으므로 잘 사용하지 않으며
ManyToMany는 향후 혹시 있을 변경에 불리하므로 사용하지 않음
양방향 연결관계에서 연결관계의 주인을 결정할때 대부분 FK가 있는쪽이 주인이 되도록 하는것이 좋다
출처 : https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런
www.inflearn.com
'Back-End > JPA' 카테고리의 다른 글
[JPA] 6. 프록시와 Cascade 및 고아 객체 (0) | 2022.08.04 |
---|---|
[JPA] 5. 상속관계 Mapping & Mapped Superclass (0) | 2022.08.03 |
[JPA] 3. Entity Mapping (0) | 2022.07.21 |
[JPA] 2. Persistent에 대한 이해 (0) | 2022.07.20 |
[JPA] 1. JPA에 대한 이해 (0) | 2022.07.20 |