[JPA] 연관관계 매핑(2) - 다양한 연관관계

0. 설계시 생각할것

- 다중성

- 단방향, 양방향

- 연관관계 주인

 

 사실 테이블에서는 외래키 하나로 양쪽 join이 가능하기 때문에 방향성이 의미를 갖지는 않는다. 반면 객체는 참조용 필드가 존재하는 쪽에서만 참조가 가능하기때문에, 한쪽만 참조하면 단방향, 양쪽이 서로 참조하면 양방향 연관관계를 가진다. 그래서 저번 포스트에서 언급했듯이, 테이블과 연관된 단방향 연관관계를 가지는 객체들을 설계한 뒤 양방향 연관관계가 필요하다면 연관관계의 주인을 정하고, 추가적인 메소드를 작성해주면 된다. 따라서 아래에는 기본이 되는 단방향 매핑만 정리하도록 하겠다.

 또한(당연한 얘기이지만) 객체 상호간에는 대칭관계를 갖는다 (한쪽이 다대일이면 반대쪽은 일대다). 

 

1. 다대일([N:1])

DB와 매핑하기 - @ManyToOne

가장 많이 사용되는 관계. fk를 가져 주인이 되는 객체가 '다(N)'쪽에 속하며 이전 포스트들은 이 형태를 베이스로 하고있다. 테이블에서 fk가 '일'의 참조를 위해 존재한다.

다대일 단방향 매핑

 

//다(Member)대 일(Team)
@Entity
public class Member {

	...
    @ManyToOne
    @JoinColumn(name = "TEAM_ID") //매핑할 fk이름. 주인(Member)객체에서 fk mapping
    private Team team;
    ...
 
}

...

@Entity
public class Team {
	...
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private String id;
    
    
}

 

2. 일대다([1:N])

'일'이 연관관계의 주인이 되어 외래키를 가지고 관리하는 형태를 말하는데, 실제로 흔히 사용되지는 않는다.

일대다 단방향 매핑. 상당히 특이하다

 

@Entity
public class Member {
	...
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    ...
}

@Entity
public class Team {
	
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    ...
    
    ...
    //@OneToMany에 @JoinColumn을 추가해서 일대다 단방향 연관관계 설정
    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<MEMBER> members = new ArrayList<>();
    ...
}

 

 

 다대일 단방향 매핑에서는 '다(Member)'쪽에 fk가 존재하여 주인이 되고, 주인쪽에서 종(Team)의 참조를 가지고 있는 형태였다면,

 일대다 단방향 매핑에서는 '일(Team)'쪽에 fk가 존재하여 주인이 되고, '일'에 해당하는 쪽에서 값을 업데이트하면 '다(Member)'의 테이블 내의 fk가 업데이트된다. 반대로 '다'에 해당하는 쪽에서는 '일'에 대한 정보를 알 수 없다.

 

이러한 방식은 개발자 입장에서 직관적이지도 않고 객체의 반대편 테이블의 외래를 관리하는 특이한 구조를 가지기 때문에 코드와도 괴리가 존재하기 때문에(추가적인 UPDATE문이 실행되어야 한다) 이 방식을 사용하지 말고 양방향 매핑이 되더라도 다대일 양방향 매핑을 사용하도록 하자.

 

 

3. 일대일([1:1])

주 테이블(자주 접근하는 테이블)과 대상 테이블 둘중에 외래키를 어디에 위치시킬지 선택이 가능한 형태.

3.1 주 테이블에 fk가 위치하는 형태

주 테이블에 외래키 위치

//대상 테이블
@Entity
public class Locker {
    @Id @GeneratedValue
    private Long id;
    
    private String name;
}
...

//주 테이블
@Entity
public class Member {
     ...
     @OneToOne //일대일
     @JoinColumn(name = "locker_id") //연관관계 생성
     private Locker locker;
     ...
}

 

다대일 단방향 매핑과 유사하게 fk가 있는 곳이 연관관계의 주인이 되며(주 객체가 대상객체의 참조를 가지는 것처럼 동작), 양방향 연관관계 설정을 위해서 반대쪽에 mappedBy를 적용시키면 된다.

 

3.2 대상 테이블에 fk가 위치하는 형태 - 양방향(단방향 불가)

대상 테이블에 외래키 위치

//대상 테이블
@Entity
public class Locker {
    @Id @GeneratedValue
    @Column(name = "locker_id")
    private Long id;
    
    private String name;
    
    @OneToOne
    @JoinColumn(name = "id") //연관관계 생성
    private Member member;
}
...

//주 테이블
@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    
    @OneToOne(mappedBy = "member") //양방향 매핑
    private Locker locker;
    ...
}

 

3.3 정리

둘 중에 어떤 방법을 써도 일대일 연관관계는 성립한다.

그렇기 때문에 주 테이블에 fk를 두고 대상 테이블을 찾는, 주 테이블에 fk를 위치시키는 방법이 객체지향적 조회의 관점에서도 좋을 수 있다. 또한 jpa 매핑이 편리하고, 조회가 잦은 주 테이블만 조회해도 대상 테이블에 데이터를 확인 가능하다.

 반대로 대상 테이블에 fk를 존재하게 하면 DB관리에 보다 편리하고, 나중에 주 테이블이 여러개의 대상 테이블을 갖도록 변경된다면, 일대다 관계로 변경하면서 테이블 구조를 유지할 수 있는 장점도 있지만 주로 접근이 이루어지는 주 테이블에서 접근시 locker까지 접근해야되서 양방향 설계를 해야되는 단점이 있다.

 

 

 

4. 다대다(N:M])

 RDB에서는 테이블 2개로 다대다 관계를 표현할 수는 없다. 대신 객체들은 컬렉션을 이용해서 객체 2개로 다대다 관계를 설정 가능하다.

 

하지만 연결 테이블에서는 단순히 연결만 하고 끝나는것이 아니라 양쪽의 두 객체간에 추가적인 정보의 교환이 이루어지기 때문에, 연결 테이블의 역할을 하는 엔티티를 직접 추가해서 관리해주면 된다.

@ManyToMany -&amp;gt; @OneToMany + @ManyToOne

또한, 위 그림에서는 MemberProduct의 MEMBER_ID, PRODUCT_ID를 묶어서 pk로 사용했지만, 되도록이면 아래 그림처럼 generated되는 id를 사용하자.(당장은 불필요한 코드를 추가하는 한이 있더라도 Id를 어딘가에 종속적으로는 만들지 말자) 시스템이 점점 커질때 만약 비즈니스적인 제약 조건이 커지면 PK를 운영중에 업데이트 하는 상황이 발생할 수도 있다.