kok202
JPA - Mapped By 의 필요성, '관계의 주인' 의미

2019. 4. 25. 00:13[정리] 기능별 개념 정리/JPA

Owner of relation != Owing side

Owner of relation 이라는 표현은 어디에서 나온 것인가?

관계를 표현하는데 있어서 가장 중요한 것은 foreign key 가 바라보는 대상이 누구인지이다.

즉 foreign key 는 관계를 나타내는데 굉장히 중요한 단서이다.

그래서 'Foreign key' = '관계의 핵심' = '관계의 주인' 이라고 한다.

 

Team ~ Member 사이의 일대다 관계가 있다고 생각해보자. 

@Entity
@Table(name="team")
public class Team {
	@Id
	@Column(name="team_id")
	private long id;
	@OneToMany(mappedBy="team")
	private List<Member> members;
}
@Entity
@Table(name="member")
public class Member {
	@Id
	@Column(name="member_id")
	private long id;
	@ManyToOne
	@JoinColumn(name="team_id")
	private Team team;
}

이 관계에서 member 테이블의 team_id 는 FK 이고 member 가 관계의 주인이다.

mappedBy는 관계의 주인이 들고 있지 않으므로 Team.members 에 mappedBy 를 표현한다. 

 

Member 는 Owing side 이다.

Member.team_id 가 관계의 주인이므로 Member.team 이 관계의 주인(Owner of relation)이다.

 

자문 : 의미적으로 Foreign key 보다 Primary key 가 관계의 주인이 되야하는 것 아닌가?

 

자답 : team 테이블 입장에서 team_id (Primary key)만으로는 자신에게 연결된 특정 관계 하나를 찾아낼 수 없다.  왜냐하면 team 테이블의 team_id 에 연결된 member 가 여러개 일 수 있기 때문이다.

반면 member 테이블 입장에서 team_id (Foreign key)가 있으면 자신의 member_id 와 조합해서 자신에게 연결된 특정 관계 하나를 찾아낼 수 있다. 즉 이런 의미에서 관계를 특정 짓는데 좀 더 강한 역할을 하는 것은 foreign key 이다. 

 

 

 

 

 

그럼 왜 관계의 주인을 지정해줘야하냐.

위에 예시에서 mappedBy가 없다고 가정해보자

@Entity
@Table(name="team")
public class Team {
	@Id
	@Column(name="team_id")
	private long id;
	@OneToMany
	private List<Member> members;
}
@Entity
@Table(name="member")
public class Member {
	@Id
	@Column(name="member_id")
	private long id;
	@ManyToOne
	@JoinColumn(name="team_id")
	private Team team;
}

두 객체 간에는 mapped by 에의한 어떤 관계인지 명시되지 않았다.  이 때 Team은 Member 리스트를 가지고 있고 Member는 Team을 가지고 있지만 두 객체는 양방향 관계가 아니다.  본질적으로 @OneToMany와 @ManyToOne과 같은 어노테이션은 하나의 단방향 관계를 표현하기 위한 어노테이션이다.  즉 두개의 단방향 관계가 있을 뿐이다. 

 

그리고 @OneToMany 어노테이션이 단방향 관계를 표현할 때 테이블을 하나 새로 만들어서 표현한다.  즉 이렇게되면 두 객체는 team, member라는 두개의 테이블을 생성하는것 뿐만 아니라 @OneToMany 단방햔 관계를 표현하기 위해 team_member 라는 세번째 테이블도 생성한다. team_member 는 스키마는 다음과 같이 생성될 것이다.

이 테이블은 Team 입장에서 Member 에 어떤 사람들이 있는지 판단하는 용도로만 사용된다. Member 입장에서 어떤 Team에 있는지 알기 위한 용도로 사용하지 않는다. 참고로 @ManyToOne 어노테이션에서 단방향 관계를 표현할 때 OneToMany 처럼 테이블을 만들지 않는다. Member 입장에서 어떤 Team에 가입되있는지 알고 싶다면 Hibernate 그냥 Member 테이블 안에 있는 team_id 칼럼을 기준으로 Team에서 검색해보면 되기 때문이다.

teamA.getMembers().add(memberA)
teamA.getMembers().add(memberB)
teamA.getMembers().add(memberC)

다시 예제로 돌아와서, 이 상태에서 위 코드를 실행했다고 생각해보자. 

team_member 테이블에는 아래 레코드들이 추가될 것이다.

(team A의 아이디값, member A 의 아이디값)

(team A의 아이디값, member B 의 아이디값)

(team A의 아이디값, member C 의 아이디값)

이를통해 teamA 에는 memberA가 있다는 것을 알게된다. 

 

그리고 만약 이때 memberA.setTeam(teamB) 를 했다고 해보자. Hibernate 는 그냥 단순히 memberA의 team_id 값을 을 team B 의 아이디 값으로 바꿀 것이다. 이는 큰 문제다. 왜냐하면 teamA 는 team_member 테이블을 통해 memberA가 여전히 teamA에 들어가있다고 생각하는데 memberA는 teamB로 변경되었기 때문이다. 단방향 관계로만 2개 존재하기 때문에 생기는 문제다.

 

이러한 문제가 생기는 원인은 결국 OneToMany 하는 단방햔 relation이 team_member 테이블을 통해 관계를 추적하고 있어서 발생하는 문제다. 

-> 즉 OneToMany 할 때 team_member 테이블을 안만들면 될 일이다.

-> 그런데 Hibernate 입장에서는 OneToMany를 설계할 때 team_member 테이블을 만들어서 추적하도록 설계해놨다.

-> OneToMany 할 때 추적하면 되는 대상을 아예 알려주면 되겠다.

 

 

 

 

 

다시 말해 mapped by의 역할은 무엇인가?

관계에대한 생성, 수정은 이미 member.team을 보고 판단하겠다고 정의(맵핑)해놨다. 여분의 테이블을 두고 추적할 필요가 없다. 라고 알려주는 것이다. 여기서 중요한 점이 생긴다. 관계에대한 생성, 수정은 mapped by에 의해서 member.team을 통해서 이루어진다고 명시했다. 그러므로 teamA-memberA 사이에 관계를 하나 추가하고 싶다했을 때 teamA.getMembers().add(memberA) 만한다고해서 관계가 추가되지 않는다. 반드시 memberA.setTeam(teamA) 를 해줘야 관계가 추가된다. mappedBy를 쓴다는 것은 개발자가 Owing side에서 제대로 Owner of relation을 통해 관계의 생성, 수정을 명시해야한다는 책임이 생긴다는 것이다. 

 

 

 

 

 

자문

왜 OneToMany 할 때 테이블을 만들어서 설계해도록 해놨는가?

그냥 처음부터 mappedBy를 쓰게하면 되지 않았나?

 

자답

Mapping에는 양방향만 존재하는게 아니다. 단방향도 존재한다.

다음과 같은 경우를 생각해보자

@Entity
@Table(name="team")
public class Team {
	@Id
	@Column(name="team_id")
	private long id;
	@OneToMany
	private List<Member> members;
}
@Entity
@Table(name="member")
public class Member {
	@Id
	@Column(name="member_id")
	private long id;
}

Member는 Team을 알 필요가 없다. 즉 Member 테이블에 외래키가 없다. 아예 MappedBy할 대상 자체가 없는 문제.

이럴때는 team_member 테이블을 만들어서 관계를 추적할 수 밖에 없다.