연관관계가 필요한 이유
다음과 같은 상황을 가정해 보자.
- 회원과 팀이 있다.
- 회원은 하나의 팀에만 소속될 수 있다.
- 회원과 팀은 다대일 관계다.
객체를 테이블에 맞추어 모델링하는 경우 (연관관계가 없는 객체)
데이터베이스는 외래키 하나로 양쪽 테이블 조인이 가능하기 때문에 위와 같이 모델링할 수 있다.
객체 연관관계도 데이터베이스 테이블 연관관계와 동일하게 모델링하였다.
코드로 나타내면 다음과 같다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@Column(name = "TEAM_ID")
private Long teamId;
…
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
…
}
객체 내부에서 서로를 참조하는 대신 외래 키를 그대로 사용하였다.
이 경우 회원의 팀을 찾기 위해서는 외래 키를 이용하여야 한다.
먼저 팀과 회원을 다음과 같이 저장을 한다.
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId());
em.persist(member);
그리고 멤버를 통해서 속한 팀을 조회하기 위해서는 먼저 멤버를 찾고 멤버의 teamId를 찾아서 다시 teamId를 통해 팀을 찾는 방법을 사용해야 한다.
//조회
Member findMember = em.find(Member.class, member.getId());
Long findTeamId = findMember.getTeamId();
Team findTeam = em.find(Team.class, findTeamId);
이러한 방식으로 코드를 짜는 것은 객체지향적이지 않을 뿐 아니라 비효율적이다.
우리가 원하는 방식은 member.getTeam()과 같이 한 번에 팀을 조회할 수 있는 방법이다.
테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾지만 객체는 참조를 이용해서 연관된 객체를 찾는다.
따라서 테이블과 객체 사이에는 이러한 간극이 있게 된다.
단방향 연관관계
회원과 팀의 관계를 객체지향적으로 모델링해보겠다.
객체 연관관계
Member는 객체 내부에 Team이라는 참조용 필드를 가지고 있다. 그래서 Member는 Team을 접근할 수 있다.
하지만 Team은 객체 내부에 Member에 관한 어떠한 정보도 가지고 있지 않다.
즉, Member에서 Team으로 가는 참조는 있지만 Team에서 Member로 가는 참조는 없다.
이러한 연관관계를 단방향 연관관계라 한다.
테이블 연관관계
데이터베이스 테이블은 외래 키 하나로 모두 테이블 조인이 가능하다.
그렇기에 굳이 단방향, 양방향을 나눌 필요 없이 외래 키로 접근할 수 있다.
단방향 연관관계를 적용하여 코드를 수정해 보겠다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
...
Member 엔티티에 teamId 대신 Team을 참조하는 필드를 생성하여 Team에 쉽게 접근할 수 있게 했다.
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); //단방향 연관관계 설정, 참조 저장
em.persist(member);
단방향 연관관계로 인해 Member에 Team을 저장할 때 참조를 그대로 저장할 수 있게 되었다.
//조회
Member findMember = em.find(Member.class, member.getId());
//참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();
Member의 Team을 조회하는 경우도 참조를 사용하여 간단하게 조회할 수 있다.
양방향 연관관계
단방향 연관관계가 한 객체만 참조용 필드를 가지고 있는 관계라면
양방향 연관관계는 두 객체가 모두 서로의 참조용 필드를 가지고 있는 관계를 의미한다.
객체 연관관계
하나의 Team에는 여러 명의 Member가 속할 수 있기 때문에 Member와 Team은 다대일 관계이다.
그래서 Team에는 Member를 참조하는 필드가 컬렉션 형태로 저장되게 된다.
Member와 Team이 서로를 참조하는 형태를 이루고 있다.
테이블 연관관계
객체 연관관계와 달리 Team 테이블에는 Member에 관한 컬럼이 없다.
그 이유는 테이블은 외래 키로 조인을 하여 멤버의 팀을 알아낼 수 있기 때문이다.
즉, 테이블은 외래 키 하나만 있으면 양방향 조회가 가능하다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
…
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
…
}
연관관계의 주인
양방향 연관관계는 단방향 연관관계 2개로 이루어진 구조와 동일한데 만약 테이블을 저장, 수정, 삭제를 할 경우에 어떤 객체가 제어의 권한을 가지는지 정해줘야 한다.
이러한 제어의 권한을 가지는 객체를 연관관계의 주인이라 한다.
연관관계의 주인이 아닌 다른 객체는 저장, 수정, 삭제가 아닌 조회만 가능하게 된다.
연관관계의 주인을 정할 때는 mappedBy를 사용하면 된다.
연관관계의 주인이 아닌 객체의 필드에 mappedBy 속성을 사용해서 속성의 값으로 연관관계 주인의 값을 넣으면 된다.
양방향 매핑 규칙
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정한다.
- 연관관계의 주인만이 외래 키를 관리(등록, 수정)한다.
- 주인이 아닌 쪽은 읽기만 가능하다
- 주인은 mappedBy 속성 사용하지 못한다.
- 주인이 아니면 mappedBy 속성으로 주인 지정한다
출처 : https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
'JPA' 카테고리의 다른 글
[JPA] 프록시와 지연로딩 (2) | 2023.03.05 |
---|---|
[JPA] @MappedSuperclass 사용법 (0) | 2023.03.03 |
[JPA] 상속관계 매핑 (0) | 2023.03.03 |
[JPA] 기본 키 매핑 방법 (0) | 2023.02.27 |
[JPA] 영속성 컨텍스트 (0) | 2023.02.26 |