JPA에서 가장 많이 사용하는 관계가 N:1 이다.
다대일 엔티티 관계를 2개의 객체를 통해 예시 코드를 정리했다.
다대일 단방향 관계 시나리오
Member 객체와 Team 객체가 있다.
멤버는 팀에 속할 수 있다. 여러 멤버는 하나의 팀에 속할 수 있다.
하나의 팀에는 여러 멤버들이 속할 수 있다.
Member 객체
package hellojpa;
import javax.persistence.*;
@Entity @Getter @Setter
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="member_id") // 실제 DB에 매핑할 칼럼명
private Long id; // 객체에서 다룰 필드명
@Column(name = "username")
private String name;
@ManyToOne //Member 객체 입장에서 '다'니까 ManyToOne
@JoinColumn(name = "team_id") // Team객체에서 참조하는 DB매핑 칼럼명 'team_id'
private Team team;
}
Team 객체
package hellojpa;
import javax.persistence.*;
@Entity @Getter @Setter
public class Team {
@Id @GeneratedValue
@Column(name="team_id")
private Long id;
private String name;
}
여러 멤버들이 하나의 팀에 속할 수 있으므로, 다대일 관계다.
다대일에서, '다'에 외래키가 있어야 한다.
따라서 Member 객체의 필드에서 Team 객체를 필드로 만들어서 참조하도록 만든다.
관계형 DB의 ERD로 생각하면, Member 테이블의 컬럼 하나가 team_id 를 FK로 가지게 된다.
객체의 연관관계로 표현하려면, FK 가 아니라 객체 참조 필드를 쓰면 된다.
Member.team 참조필드로 다대일 단방향 연관관계를 맺을 수 있다.
이렇게 Member 객체가 Team 객체를 참조할 수 있다.
다대일 양방향 관계 시나리오
이 팀에 속한 멤버들 목록을 알고 싶다.
즉, Team 객체에서 List< Member> 를 필드로 가진다.
단방향과의 차이
단방향 : Member <- Team
양방향 : Member <- Team, Team <- Member 단방향 관계 2개를 설정하면 양방향이다.
단, DB의 ERD 관점에서 설계가 변하지 않는다.
관계형 데이터베이스 에서는 FK하나를 가지고, 조인하면 두 테이블 모두를 자유롭게 조회할 수 있다.
관계형 데이터베이스 테이블을 보면 '방향'개념이 없다.
Member 객체
package hellojpa;
import javax.persistence.*;
@Entity @Getter @Setter
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="member_id")
private Long id;
@Column(name = "username")
private String name;
@ManyToOne // Member 엔티티 입장 중심으로 적는다.
@JoinColumn(name = "team_id") // Team 엔티티에서 참조하는 실제컬럼명 (FK이름을) 적는다.
private Team team;
}
Team 객체
package hellojpa;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Team {
@Id @GeneratedValue
@Column(name="team_id")
private Long id;
private String name;
@OneToMany(mappedBy = "team") // 일대다 매핑에서, 상대팀 엔티티의 어느 필드를(칼럼을) 참조하는지 적는다.
private List<Member> members = new ArrayList<>();
/** 연관관계 편의 메소드 Team이나 Member 한 쪽에만 구현하기! */
public void addMember(Member member){
member.setTeam(member.getTeam());
members.add(member);
}
}
연관관계의 주인이란?
연관관계의 주인은 '양방향' 연관관계에서 다루는 개념이다.
외래키를 사용하는 쪽이 연관관계의 주인이다.
여기서, Member.team이 연관관계의 주인이다.
mappedBy속성에는 자신을 매핑하고 있는 주인엔티티의 칼럼명을 적어준다. (Member의 team)
주인이 아닌 쪽은 읽기만 가능하다. 그리고 mappedBy 속성으로 주인을 지정해줘야 한다.
연관관계 편의 메소드
Member도 Team을 참조하고, Team도 Member를 참조한다.
그래서 값을 삽입할 때 헷갈리는 부분이 있다.
양방향 연관관계에서, 한 쪽을 선택하여 연관관계 편의 메소드를 작성하자.
예를 들어, Member가 속한 Team을 세팅 해 줄 때, 반대편 Team에도 member가 속함을 세팅하자.
유지보수 편의를 위함이다.
1. Member 객체에 구현
Member를 추가할 때, 반대편에 Team 객체에도 members List에 새 멤버를 추가한다.
public void changeTeam(Team team){
this.team = team;
team.getMembers().add(this); // 멤버 객체의 members에 '나 자신'을 추가한다.
}
2. Team 객체에 구현
Team 객체를 추가할 때 member 객체를 추가한다.
public void addMember(Member member){
member.setTeam(member.getTeam());
members.add(member);
}
둘 중 한 곳에만 연관관계 편의 메소드를 작성하자. 양 쪽에 작성하면 버그 발생할 확률이 높다.