이전에 말했듯이 JPA는 객체와 DB의 패러다임 불일치로 인한 불편을 없애고 객체를 마치 Collections에 저장하는 것처럼 DB에 저장할 수 있게 하기 위해서 등장했다.

 

개발을 하다보면 필연적으로 연관관계를 가진 테이블들이 존재할 수밖에 없게 되는데 이에 대해선 어떻게 처리해야 할까?

 

N : 1의 관계로 이루어진 Member와 Team이 있다고 하자. 하나의 Team에 여러 Member가 소속될 수 있고 이러한 관계를 나타내기 위해 Member가 Team의 PK를 FK로 가지게 될 것이다. 

이러한 방식을 따라 클래스를 설계하면 아래와 같은 형태가 된다.

@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String name;

    @Column(name = "TEAM_ID")
    private Long teamId;
}

 

그런데 이는 DB에 종속적으로 맞춘 설계이다. 즉, 객체 지향적인 설계라고 볼 수 없다.

 

위와 같이 설계를 한다면 em.find(memberId) 로 member를 찾고, member.getTeamId()를 통해 teamId를 찾고 다시 그걸 이용해 em.find(teamId)로 해당 멤버가 속한 팀을 찾아야 한다.

 

하지만 여태껏 자바 프로그래밍을 해왔던 대로라면 Member 안에 Team 객체에 대한 변수를 두어 참조 관계를 형성하고 member에서 바로 Team 객체에 접근하도록 하는 것이 우리가 지금까지 해왔던 객체지향적인 설계이다.

 

앞서 언급했다시피 JPA는 이러한 패러다임 불일치를 없애고 객체 지향적인 설계에 집중할 수 있도록 도와주는 도구이다.

 

아래와 같은 방법으로 객체 지향적인 모델링을 할 수 있다.

@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String name;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

 

참조 관계처럼 Member가 Team team을 직접 소유한다. 그리고 @ManyToOne을 통해 Member : Team의 N : 1 관계를 표현한 후 @JoinColumn을 통해 DB 내에서 Member 테이블이 FK로 가질 Team 테이블의 PK를 지정해준다.

 

위와 같은 방식의 연관관계 매핑을 '단방향 연관관계'라고 한다. Member는 참조 변수 team을 통해 자신이 속한 Team에 접근이 가능하지만 Team에선 자신에게 속한 Member들에 접근이 불가하기 때문에 단방향이라는 이름이 붙었다.

 

 

그런데 일반적인 관계형 데이터베이스에서는 항상 양방향 연관관계를 지원한다. 조인된 테이블을 조회할 때를 생각해보자면.. Member는 자신이 FK로 teamId를 사용해 Team을 조회할 수 있고, Team은 자신의 PK를 FK로 가진 Member들을 검색하는 방식으로 자신에 속한 Member들에 접근이 가능하다.

 

이러한 양방향 연관관계는 아래와 같이 표현할 수 있다.

@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<>();
}

 

Member의 List를 참조변수로 둠으로써 양방향 연관관계를 나타냈다.

(이때 new ArrayList<>()로 굳이 초기화를 해준 이유는 관례 상 대부분 그렇게 하기 때문)

 

주의할 것은, 이번에는 Member가 아닌 Team 기준이기 때문에 @ManyToOne이 아니라 @OneToMany를 사용해야 한다는 것이다. 이때 속성으로 주어진 mappedBy에는 Member에서 자신을 참조하고 있는 참조변수의 이름을 넣어주는 것이다. (Team team)

 

 

사실 위와 같은 경우를 편의상 양방향 연관관계라고 말하기는 하지만 진정한 양방향 연관관계라고 볼 수는 없다.

단방향 연관관계 두개를 두어 양방향 연관관계처럼 사용하도록 한 것이다.

 

 

 

그런데 여기까지 왔다면 몇 가지 의문점이 생긴다.

1. 왜 Member에서는 @JoinColumn(name = "TEAM_ID")로 관계를 표현하고 Team에서는 @OneToMany(mappedBy = "team")관계를 표현하는가?  (물론 Member에도 @ManyToOne이 있긴 하지만)

2. 두 테이블을 잇는 TEAM_ID라는 컬럼은 Member, Team 중 어느 쪽에서 관리해야 하는가?

 

위와 같은 문제는 양방향 연관관계에서 항상 발생하게 되는데 이것을 이해하기 위해 "연관관계의 주인(Owner)" 이라는 것을 정해야 한다. 양방향 관계를 완성하는 두 개의 단방향 관계 중 하나의 관계를 주인으로 지정하는 것이다.

 

일반적으로 FK를 가진 곳을 관계의 주인으로 잡는다. 즉, 1 : N 관계의 경우 N쪽으로 주인으로 하는 것이다.

(일반적으로라고 했지만 항상 이를 따르자) 

 

그리고 이에 따라 아래와 같은 규칙들이 정해진다.

1. FK는 연관관계의 주인만이 관리한다.

2. 주인이 아닌 나머지 쪽은 FK에 대해 읽기만 수행할 수 있다.

3. 주인이 아닌 쪽에서 mappedBy 속성을 사용한다. (속성의 이름부터가 수동적인 느낌이라 주인에게는 어울리지 않는다. mappedBy = "관계의 주인") 

 

 

+ Recent posts