[JPA] 다양한 연관관계 매핑

사명기·2019년 11월 20일
9

이번 글에서는 <다양한 연관관계 매핑>에 대해 알아보겠습니다.
이 시리즈 글은 김영한 님의 강의, 을 보고 적은 것임을 알려드립니다. (강추)

  • 다중성
    연관관계는 다음과 같은 다중성이 있습니다.
    다대일(@ManyToOne), 일대다(@OneToMany), 일대일(@OneToOne), 다대다(@ManyToMany)
    보통 다대일과 일대다 관계를 가장 많이 사용하고 다대다 관계는 실무에서 거의 사용하지 않습니다.

  • 단방향, 양방향
    테이블은 외래 키 하나로 조인을 사용해서 양방향으로 쿼리가 가능하므로 사실상 방향이라는 개념이 없습니다.
    반면에 객체는 참조용 필드를 가지고 있는 객체만 연관된 객체를 조회할 수 있습니다.
    객체 관계에서 한 쪽만 참조하는 것을 단방향 관계라 하고, 양쪽이 서로 참조하는 것을 양방향 관계라 합니다.

  • 연관관계의 주인
    테이블은 외래 키 하나로 두 테이블의 연관관계를 맺습니다. 따라서 테이블의 연관관계를 관리하는 포인트는 외래 키 하나입니다.
    반면에 객체 양방향 관계는 A->B, B->A 처럼 참조가 2군데입니다. JPA는 두 객체 연관관계 중 하나를 정해서 데이터베이스 외래 키를 관리하는데 이것을 연관관계의 주인이라 합니다. 외래 키를 가진 테이블과 매핑한 엔티티가 외래 키를 관리하는 게 효율적이므로 보통 이곳을 연관관계의 주인으로 선택합니다. 주인이 아닌 방향은 외래 키를 변경할 수 없고 읽기만 가능합니다.
    연관관계의 주인이 아니면 mappedBy 속성을 사용하고 연관관계의 주인 필드 이름을 값으로 입력해야 합니다.

1.다대일


다대일 관계의 방향은 항상 일대다 관계고 일대다 관계의 반대 방향은 항상 다대일 관계입니다.
데이터베이스 테이블의 일(1), 다(N) 관계에서 외래 키는 항상 다쪽에 있습니다. 따라서 객체 양방향 관계에서 연관관계의 주인은 항상 다쪽입니다.

1.1 다대일 단방향

1.2 다대일 양방향 [N:1, 1:N]

  • 양방향은 외래 키가 있는 쪽이 연관관계의 주인입니다.
    일대다와 다대일 연관관계는 항상 다(N)에 외래 키가 있습니다. 여기서는 다쪽인 MEMBER 테이블이 외래 키를 가지고 있으므로 Member.team이 연관관계의 주인입니다. JPA는 외래 키를 관리할 때 연관관계의 주인만 사용합니다. 주인이 아닌 TEAM.members는 조회를 위한 JPQL이나 객체 그래프를 탐색할 때 사용합니다.

  • 양방향 연관관계는 항상 서로를 참조해야 합니다.
    양방향 연관관계는 항상 서로 참조해야 합니다. 어느 한 쪽만 참조하면 양방향 연관관계가 성립하지 않습니다. 항상 서로 참조하게 하려면 연관관계 편의 메서드를 만드는 것이 좋습니다.

2.일대다


일대다 관계는 다대일 관계의 반대 방향입니다. 일대다 관계는 엔티티를 하나 이상 참조할 수 있으므로 자바 컬렉션인 Collection, List, Set, Map 중에 하나를 사용해야 합니다.

2.1 일대다 단방향[1:N]

하나의 팀은 여러 회원을 참조할 수 있는데 이런 관계를 일대다 관계라 합니다.
그리고 팀은 회원들을 참조하지만 반대로 회원은 팀을 참조하지 않으면 둘의 관계는 단방향입니다.

일대다 단방향은 일대다(1:N)에서 일(1)이 연관관계의 주인입니다.
테이블 일대다 관계는 항상 다(N) 쪽에 외래 키가 있습니다. 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조입니다.
일대다 단방향 관계를 매핑할 때는 @JoinColumn을 꼭 사용해야 하빈다. 그렇지 않으면 JPA는 연결 테이블을 중간에 두고 연관관계를 관리하는 조인 테이블 전략을 기본으로 사용해서 매핑합니다.(중간에 테이블 하나 추가함)

  • 일대다 단방향 매핑의 단점
    일대다 단방향 매핑의 단점은 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있다는 점입니다.
    본인 테이블에 외래 키가 있으면 엔티티의 저장과 연관관계 처리를 INSERT SQL 한 번으로 끝낼 수 있지만, 다른 테이블에 외래 키가 있으면 연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야 합니다.
  • 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용
    일대다 단방향 매핑을 사용하면 엔티티를 매핑한 테이블이 아닌 다른 테이블의 외래 키를 관리해야 합니다.
    이것은 성능 문제도 있지만 관리도 부담스럽습니다. 문제를 해결하는 좋은 방법은 일대다 단방향 매핑 대신에 다대일 양방향 매핑을 사용하는 것입니다.
    다대일 양방향 매핑은 관리해야 하는 외래 키가 본인 테이블에 있습니다. 따라서 일대다 단방향 매핑 같은 문제가 발생하지 않습니다. 두 매핑의 테이블 모양은 완전히 같으므로 엔티티만 약간 수정하면 됩니다.

2.2 일대다 양방향


이런 매핑은 공식적으로 존재하지 않습니다.
@JoinColumn(insertable=false, updatable=false)
읽기 전용 필드를 사용해서 양방향 처럼 사용하는 방법입니다.
결론: 다대일 양방향을 사용하자

3.일대일


일대일 관계는 양쪽이 서로 하나의 관계만 가집니다. 예를 들어 회원은 하나의 사물함만 사용하고, 사물함도 하나의 회원에 의해서만 사용됩니다.

일대일 관계의 특징

  • 일대일 관계는 그 반대도 일대일 관계입니다.

  • 테이블 관계에서 일대다, 다대일은 항상 다(N)쪽이 외래 키를 가집니다. 반면에 일대일 관계는 주 테이블이나 대상 테이블 둘 중 어느곳이나 외래 키를 가질 수 있습니다.

  • 외래 키에 데이터베이스 유니크(UNI) 제약조건 추가되야 합니다.

    일대일 관계는 주 테이블이나 대상 테이블 중에 누가 외래 키를 가질지 선택해야 합니다.

  • 주 테이블에 외래 키
    주 객체가 대상 객체를 참조하는 것처럼 주 테이블에 외래 키를 두고 대상 테이블을 참조합니다.
    외래 키를 참조와 비슷하게 사용할 수 있어서 객체지향 개발자들이 선호합니다.
    이 방법의 장점은 주 테이블이 외래 키를 가지고 있으므로 주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있습니다.

  • 대상 테이블에 외래 키
    전통적인 데이터베이스 개발자들은 보통 대상 테이블에 외래 키를 두는 것을 선호합니다.
    이 방법의 장점은 테이블 관계를 일대일에서 일대다로 변경할 때 테이블 구조를 그대로 유지할 수 있습니다.

3.1 주 테이블에 외래 키

일대일 관계를 구성할 때 객체지향 개발자들은 주 테이블에 외래 키가 있는 것을 선호합니다.
JPA도 주 테이블에 외래 키가 있으면 좀 더 편하게 매핑할 수 있습니다.

단방향

MEMBER가 주 테이블이고 LOCKER는 대상 테이블입니다.
회원은 하나의 Locker만 가지고, Locker는 하나의 회원에 의해서만 사용되는 경우입니다.
@OneToOne 어노테이션을 사용하고, 이 관계는 다대일 단방향(@ManyToOne)과 거의 비슷합니다.

양방향

양방향이므로 연관관계의 주인을 정해야 합니다. MEMBER 테이블이 외래 키를 가지고 있으므로 Member 엔티티에 있는 Member.locker가 연관관계의 주인입니다.
따라서 반대 매핑인 사물함의 Locker.member는 mappedBy를 선언해서 연관관계의 주인이 아니라고 설정해야 합니다.

다대일 양방향 매핑 처럼 **왜래 키가 있는 곳이 연관 관계의 주인이 되고, 반대편은 mappedBy 적용

3.2 대상 테이블에 외래 키

단방향

위처럼 일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는 JPA에서 지원하지 않습니다. 그리고 이런 모양으로 매핑할 수 있는 방법도 없습니다. 따라서 이 때는 단방향 관계를 Locker에서 Member 방향으로 수정하거나, 양방향 관계로 만들고 Locker를 연관관계의 주인으로 설정해야 합니다.

양방향

일대일 매핑에서 대상 테이블에 외래 키를 두고 싶으면 이렇게 양방향으로 매핑합니다. 주 엔티티인 Member 엔티티 대신에 대상 엔티티인 Locker를 연관관계의 주인으로 만들어서 LOCKER 테이블의 외래 키를 관리하도록 했습니다.

일대일 관계 정리
주 테이블에 외래 키
- 주 객체가 대상 객체의 참조를 가지는 것처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾음
- 객체지향 개발자 선호
- JPA 매핑 편리
- 장점: 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
- 단점: 값이 없으면 외래 키에 null 허용
대상 테이블에 외래 키
- 대상 테이블에 외래 키가 존재
- 전통적인 데이터베이스 개발자 선호
- 장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
- 단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨

4.다대다

"다대다는 실무에서 쓰면 안된다고 봅니다." - 김영한님

관계형 데이터베이스는 정규화된 테이블 2개로 다대다를 표현할 수 없습니다.
그래서 보통 다대다 관계를 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용합니다.

예를 들어 회원들은 상품을 주문합니다. 반대로 상품들은 회원들에 의해 주문 됩니다.
둘은 대다대 관계입니다. 따라서 회원 테이블과 상품 테이블만으로 이 관계를 표현할 수 없습니다.
그래서 아래 그림처럼 중간에 연결 테이블을 추가해야 합니다. 이 테이블을 사용해서 다대다 관계를 일대다, 다대일 관계로 풀어낼 수 있습니다.

그런데 객체는 테이블과 다르게 객체 2개로 다대다 관계를 만들 수 있습니다. 예를 들어, 회원 객체는 컬렉션을 사용해서 상품들을 참조하면 되고, 반대로 상품들도 컬렉션을 사용해서 회원들을 참조하면 됩니다.
@ManyToMany를 사용하면 아래 그림처럼 다대다 관계를 편리하게 매핑할 수 있습니다.

다대다 매핑의 한계

  • 편리해 보이지만 실무에서 사용하기엔 한계가 있습니다
    예를 들어 회원이 상품을 주문하면 연결 테이블에 단순히 주문한 회원 아이디와 상품 아이디만 담고 끝나지 않습니다. 보통은 연결 테이블에 주문 수량 컬럼이나 주문한 날짜 같은 컬럼이 더 필요합니다.

위와 같이 연결 테이블에 주문 수량(ORDERAMOUNT)나 주문 날짜(ORDERDATE) 컬럼을 추가했습니다. 이렇게 컬럼을 추가하면 더는 @ManyToMany를 사용할 수 없습니다. 왜냐하면 주문 엔티티나 상품 엔티티에는 추가한 컬럼들을 매핑할 수 없기 때문입니다.

다대다 한계 극복

  • **연결 테이블용 엔티티 추가(연결 테이블을 엔티티로 승격)
  • @ManyToMany -> @OneToMany, @ManyToOne

0개의 댓글