연관관계 매핑 기초
목표 🤔
- 객체와 테이블 연관관계의 차이를 이해
- 객체의 참조와 테이블의 외래 키를 매핑
객체가 지향하는 패러다임과 관계가 지향하는 패러다임의 차이를 이해하고 적절하게 매핑하는 방법을 이해한다.
용어
방향(Direction) : 단방향, 양방향
다중성(Multiplicity) : 다대일, 일대다, 일대일, 다대다
연관관계의 주인(Owner) : 객체 양방향 연관관계는 관리 주인이 필요함
연관관계가 필요한 이유
[예제 시나리오]
- 회원과 팀이 있다.
- 회원은 하나의 팀에만 소속될 수 있다.
- 회원과 팀은 다대일 관계이다.
@Entity
public class Member{
...
private Long teamId;
...
}
연관관계가 없다면, 엔티티에 매번 외래키를 매핑해야 하고, 연관된 테이블을 찾기 위해 외래키를 통해 조회해야 한다. 즉 객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 패러다임의 불일치 발생 (테이블은 외래키로 조회 vs 객체는 참조를 통해 조회)
간단한 연관관계 예시
다대일 관계(단방향)를 @ManyToOne, @JoinColumn 어노테이션을 통해 표현할 수 있다.
@Entity
public class Member{
...
@ManyToOne
@JoinColumn
private Team team;
}
-------------------------------------
// Team 생성
Team team = new Team("Team A");
Team team2 = new Team("Team B");
// Member 생성, 연관관계 매핑
Member member = new Member();
member.setTeam(team);
// Member 엔티티로부터 Team을 조회할 때
Member foundMember = em.find(Member.class, 1L);
Team foundTeam = foundMember.getTeam();
// Member 엔티티 새로운 Team 매핑
foundMember.setTeam(team2);
멤버와 팀의 다대일 관계를 표현하고, 멤버로부터 팀을 가져오거나 팀을 설정할 수 있다.
단방향과 양방향
테이블 연관관계에서는 외래 키만 있다면 서로 찾아갈 수 있기에 단방향, 양방향이라는 개념이 없다. 하지만 객체는 방향이 없다면 서로의 참조관계를 알 수 없다.
상단의 코드는 Member 엔티티는 Team을 알 수 있지만, Team은 Member를 알 수 없었다. (단방향) Team에서 어떤 Member들이 속하고 있는지 알 수 있도록 양방향으로 구성하는 방법을 알아본다.
@Entity
public class Team{
...
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<>();
}
@OneToMany를 통해 Team을 참조하고 있는 Member List를 가져올 수 있도록 양방향을 설정하였다. mappedBy 속성은 Member 엔티티에서 Team의 field명을 적어준다.
연관관계의 주인과 mappedBy
객체와 테이블이 각각 연관관계를 맺는 차이를 이해하면 사용하기 쉽다.
- 객체 연관관계 (2개)
- 회원 → 팀 (단방향)
- 팀 → 회원 (단방향)
- 테이블 연관관계 1개
- 회원 <-> 팀 (양방향)
양방향 매핑
객체의 두 관계중 하나를 연관관계의 주인으로 지정해야 함. 즉 주인이 외래 키를 관리(등록, 수정)하고, 주인이 아닌 쪽은 외래 키를 읽기만 가능해야 한다.
규칙
- 외래 키가 있는 곳을 주인으로 정한다.
- 주인은 mappedBy 속성을 사용하지 않는다.
- 주인이 아니면 mappedBy 속성으로 주인을 지정한다.
@Entity
public class Member {
...
@OneToMany(mappedBy = “주인”)
List<Member> members = new ArrayList<>();
}
// 1. Member는 주인이 아니니까 mappedBy 속성을 통해 Team을 주인으로 정했고
// 2. members는 읽기만 가능하다.
@Entity
public class Member {
...
@ManyToOne
Team team;
}
// 1. Team은 주인이다. mappedBy 속성을 지정하지 않는다.
// 2. Team을 get/set 하여 참조하거나 참조하도록 설정할 수 있다.
주의점
- 연관관계를 매핑한다.
- 순수한 객체 관계를 고려하면 항상 양쪽 다 값을 입력해야 함
- Member에 member.setTeam(team);
- Team에도 findTeam.getMembers().add(member);
- 무한루프 주의 (양쪽 엔티티에 toString(), json 생성 등을 사용하지 말 것)
한 쪽만 연관관계 매핑 시 오류가 나는 시나리오 예시
- 새로운 멤버를 생성하고 팀을 매핑 시킨다.
- 팀의 멤버들을 알아보기 위해 1차 캐시에서 팀을 가져온다. (DB에서 새로 find한 것이 아닌!)
- 이때 가져온 팀은 새로운 멤버를 포함하지 않은 팀이다.
올바른 시나리오
- 새로운 멤버를 생성하고 팀을 매핑 시킨다.
- 팀에 새로운 멤버를 추가한다.
- 팀의 멤버들을 알아보기 위해 1차 캐시에서 팀을 가져온다.
- 이때 가져온 팀은 새로운 멤버를 포함하지 않은 팀이다.
이를 보완하기 위한 “연관관계 편의 메소드”
@Entity
public class Member{
...
public void changeTeam(Team team){
this.team = team;
team.getMembers().add(this);
}
}
// 혹은
@Entity
public class Team{
...
public void addMember(Member member){
member.setTeam(team);
members.add(member);
}
}
- 연관관계 매핑하는 메소드의 이름은 “setXXX()”를 사용하지 않는다. 단순히 “setXXX()”로 하면 어떤 로직이 존재하지 않고 값만 변하는 것으로 보이기 때문에 일부러 “changeXXX()” 라는 이름의 메소드를 사용
- 양쪽 관계를 보완하기 위한 코드를 작성한다.
- 연관관계 편의 메소드는 “一” 쪽에 작성해도 되고 “多”쪽에 작성해도 되나 무한루프 등을 방지하기 위해 양쪽에 사용하지는 않는다.
- “一”을 기준으로 “多”를 설정할지, “多”를 기준으로 “一”을 넣을지는 본인이 결정
정리
단방향 매핑만으로 이미 연관관계 매핑은 완료한 것이다. (양방향은 추가하면 되니까)
단방향 매핑으로 다 끝낸다는 생각으로 설계를 끝내고, 필요할 때 고민하고 양방향을 추가한다.
연관관계의 주인은 비즈니스 로직을 기준으로 선택하지 않고, 외래 키의 위치를 기준으로 정해야 한다.
'# 강의 > [인프런] 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
[完] 값 타입 (0) | 2022.05.09 |
---|---|
프록시와 연관관계 관리 (0) | 2022.05.09 |
고급 매핑(상속, Mapped Superclass) (0) | 2022.02.20 |
엔티티 매핑 1(테이블, 컬럼, 키 매핑 전략) (0) | 2022.01.12 |
영속성 관리 - 내부 동작 방식 (0) | 2022.01.12 |
JPA는 왜 사용해야 하는가? (0) | 2022.01.06 |