본문 바로가기
Today I Learned/Spring

[Spring] JPA 엔티티 간 연관 관계 매핑

by 프로그래 밍구 2022. 9. 25.

 엔티티 클래스 간의 관계를 만들어주는 것을 연관 관계 매핑이라고 한다. 연관 관계 매핑은 참조하는 방향성을 기준으로 단방향 연관 관계와 양방향 연관 관계로 구분할 수 있다. 그리고 참조할 수 있는 객체의 수에 따라서 일대다(1:N)다대일(N:1)다대다(N:N)일대일(1:1)의 연관 관계로 나눌 수 있다. 카페에서 커피를 주문할 수 있는 커피주문 애플리케이션에서 회원 정보인 Member 클래스와 주문 정보인 Order 클래스의 관계를 살펴보겠다.

단방향 연관 관계

 

[그림 1] Member가 Order을 참조할 수 있는 단방향 관계

 

 위 그림에서 Member 클래스는 Order 객체를 원소로 포함하고 있는 List 객체를 가지고 있으므로 Order를 참조할 수 있다. 즉 Member는 Order의 정보를 알 수 있다. 반면에 Order 클래스는 Member 클래스에 대한 참조 값이 없으므로 Order 입장에서는 Member 정보를 알 수 없다. 이처럼 한쪽 클래스만 다른 쪽 클래스의 참조 정보를 가지고 있는 관계를 단방향 연관 관계라고 한다.

양방향 연관 관계

 

[그림 2] Order와 Member가 서로 참조할 수 있는 양방향 관계

 

 위 그림에서 Member 클래스가 Order 객체를 원소로 포함하고 있는 List 객체를 가지고 있으며 Member 클래스를 참조할 수 있다. 그리고 Order 클래스 역시 Member 객체를 가지고 있으므로, Member 클래스를 참조할 수 있다. 따라서 두 클래스 모두 서로의 객체를 참조할 수 있으므로 Member는 Order의 정보를 알 수 있고, Order는 Member의 정보를 알 수 있다. 이처럼 양쪽 클래스가 서로의 참조 정보를 가지고 있는 관계를 양방향 연관 관계라고 한다.

참고: JPA는 단방향 연관 관계와 양방향 연관 관계를 모두 지원하는 반면에 Spring Data JDBC는 단방향 연관 관계만 지원한다.

 

 

[그림 3] Member와 Order의 일대다 단방향 관계

일대다 단방향 연관 관계

 일대다의 관계란 일(1)에 해당하는 클래스가 다(N)에 해당하는 객체를 참조할 수 있는 관계를 의미한다. 그림과 같이 한 명의 회원이 여러 건의 주문을 할 수 있으므로 Member와 Order는 일대단 관계이며 또한 Member만 List 객체를 참조할 수 있으므로 단방향 관계이다. 

그런데 결론부터 이야기하면 일대다 단방향 매핑은 잘 사용하지 않는다.

 

 

[그림 4] MEMBER 테이블과 ORDERS 테이블의 관계

 

 테이블 간에 관계에서는 일대다 중에서 '다'에 해당하는 테이블에서 '일'에 해당하는 테이블의 기본키를 외래키로 가진다. 따라서 ORDERS 테이블이 MEMBER 테이블의 기본키인 member_id를 외래키로 가진다.

 그런데 [그림 3번]에서 Order 클래스가 '테이블 관계에서 외래키에 해당하는 MEMBER 클래스의 참조값'을 가지고 있지 않아 일반적인 테이블 간의 관계를 정상적으로 표현하지 못하고 있다. 따라서 Order 클래스의 정보를 테이블에 저장하더라도 외래키에 해당하는 MEMBER 클래스의 memberId 값이 없는 채로 저장이 된다. 이러한 문제 때문에 단방향 매핑은 잘 사용하지 않는다.

 일대다 단방향 매핑 하나만 사용하는 경우는 드물지만 다대일 단방향 매핑을 먼저 한 후에 필요한 경우 일대다 단방향 매핑을 추가해서 양방향 연관 관계를 만드는 것이 일반적이다.

 다대일 연관 관계

 

[그림 5] Order와 Member의 다대일 단방향 관계

 

 다대일 관계란 다(N)에 해당하는 클래스가 일(1)에 해당하는 객체를 참조할 수 있는 관계를 의미한다. 위 그림과 같이 여러 건의 주문은 한 명의 회원에 속할 수 있으므로 Order와 Member는 다대일 관계이고, Order만 Member 객체를 참조할 수 있으므로 단방향 관계이다. 다대일 단방향 매핑은 테이블 간의 관계처럼 자연스러운 매핑 방식이기 때문에 JPA의 엔티티 연관 관계 중에서 가장 기본으로 사용되는 매핑 방식이다.


다음으로 코드로 다대일 연관 관계 매핑 방법을 알아보겠다.

다대일 연관 관계 매핑 방법

@NoArgsConstructor
@Getter
@Setter
@Entity(name = "ORDERS")
public class Order {
    //...
    @ManyToOne   // (1)
    @JoinColumn(name = "MEMBER_ID")  // (2)
    private Member member;
    //...
}

[코드 1] 다(N)에 해당하는 Order 클래스의 연관관계 매핑

 

 위 코드와 같은 방법으로 매핑을 할 수 있다. (1)과 같이 @ManyToOne 애너테이션으로 다대일의 관계를 명시한다. 그리고 (2)와 같이 @JoinColumn 애너테이션으로 ORDERS 테이블에서 외래키에 해당하는 컬럼명을 적어준다. 일반적으로 부모 테이블에서 기본키로 설정된 칼럼명과 동일하게 외래키 컬럼을 만든다. 다대일 단방향 연관 관계이기 때문에 위와 같이 다(N) 쪽에서만 설정을 하면 매핑 작업은 끝난다.

다대일 매핑에 일대다 매핑 추가

카페 입장에서 주문을 누가 했는지 주문한 회원의 회원 정보를 알아야할 경우에는 다대일 매핑을 통해 주문한 사람의 정보를 조회할 수 있다. 그런데 회원 입장에서는 자신이 주문한 주문 목록을 확인할 수 있어야 할텐데 다대일 매핑만으로는 member 객체를 통해 order 객체들을 조회할 수 없다.
이러한 경우, 다대일 매핑이 되어 있는 상태에 일대다 매핑을 추가해 양방향 관계를 만들어주면 된다.

 

@NoArgsConstructor
@Getter
@Setter
@Entity
    public class Member{
    //...
    @OneToMany(mappedBy = "member") //(1)
    private List<Order> orders = new ArrayList<>();
    //..
}

[코드 2] 일대다에서 일(1)에 해당하는 Member 클래스

 

 @OneToMany(mappedBy = "member")를 주목하자. 일대다 단방향 매핑의 경우에는 mappedBy 애트리뷰트의 값이 필요하지 않다. mappedBy의 값은 관계를 소유하고 있는 필드를 지정하는 것이다. MEMBER 테이블과 ORDER 테이블의 관계에서 ORDER 테이블의 외래키로 MEMBER 테이블의 기본키 컬럼인 MEMBER_ID의 값을 지정한다. 그렇다면 Order 클래스에서 외래키의 역할을 하는 필드는 member 필드이다. 그렇게 때문에 위 코드에서 mappedBy의 값은 member이 된다.


다대다 연관 관계

 위 예시로 쓰였던 커피 주문 샘플 애플리케이션에서도 주문(Order)과 커피(Coffee)의 관계는 다대다 관계이다. 하나의 주문에 여러 개의 커피가 속할 수 있고, 하나의 커피는 여러 주문에 속할 수 있으니 다대다 관계인 것이다. 그럼 JPA에선 다대다 해당하는 엔티티 클래스는 어떻게 매핑해야할까?
일반적인 방법으로는 테이블 설계 시, 중간에 테이블을 하나 추가해서 두 개의 일대다 관계를 만들어주는 것이 일반적인 방법이다.

 

[그림 6] 다대다 관계의 테이블을 두 개의 일대다 관계로 설계한 예

 

 위 그림에서는 다대다 관계에 있는 ORDERS 테이블과 COFFEE 사이에 ORDER_COFFEE 테이블을 두고 두 개의 일대다 관계로 만들었다. ORDER_COFFEE 테이블은 ORDERS 테이블의 외래키와 COFFEE 테이블의 외래키를 가지고 있다. 테이블 설계가 되었으니 클래스 간의 연관 관계 매핑을 하면 된다. 그런데 일대다 단방향 매핑은 외래키를 포함하지 않기 때문에 자주 사용되지 않는 매핑 방법이다. 그렇다면 두 개의 다대일 매핑이 필요하다. 그리고 나서도 현실적으로 다대일 매핑을 통해 객체 그래프 탐색으로 원하는 객체를 조회할 수 없다면 그때 일대다 양방향 매핑을 추가하면 된다.

일대일 연관 관계

 일대일 연관 관계 매핑은 단방향 연관 관계 매핑과 매핑 방법이 거의 동일하다. @ManyToOne 애너테이션 대신 @OneToOne 애너테이션을 사용하면 된다. 일대일 단방향 매핑에 양방향 매핑을 추가하는 방법도 다대일에 일대다 매핑을 추가하는 방식과 동일하다. 이때 역시 @OneToOne 애너테이션을 사용한다.

'Today I Learned > Spring' 카테고리의 다른 글

[Spring] Spring Security  (0) 2022.09.27
[Spring] API 문서화  (2) 2022.09.26
[Spring] DTO(Data Transfer Object)  (0) 2022.09.24
[Spring] Controller  (0) 2022.09.23
[Spring] Spring MVC 아키텍처  (2) 2022.09.22

댓글