개발/Spring

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

dongdev 2022. 5. 13. 00:18

 

연관관계 매핑시 고려사항 3가지

  • 다중성 
  • 단방향, 양방향
  • 연관관계의 주인

다중성 : 다대일(@ManyToOne), 일대다(@OneToMany), 일대일(@OneToOne), 다대다(@ManyToMany)

단방향, 양방향 : 테이블은 외래 키 하나로 양쪽 조인이 가능하다. 사실상 방향이라는 개념이 없다. 하지만 객체는 필드가 있는 쪽으로만 참조가 가능하다. 한쪽만 참조하면 단방향관계, 양쪽이 서로 참조하면 양방향관계이다.

연관관계의 주인 : 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺는다. 따라서 테이블의 연관관계를 관리하는 포인트는 외래 키 하나이다. 하지만 객체 양방향 관계는 A->B, B->A 처럼 참조가 2군데이다. 따라서 둘중 테이블의 외래 키를 관리할 연관관계의 주인을 지정해야한다. 보통 외래 키를 가진 테이블과 매핑한 엔티티가 외래 키를 관리한다.

주인의 반대편은 외래 키에 영향을 주지 않고 단순 조회만 가능하다.

1. 다대일

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

1.1 다대일 단방향

가장 많이 사용하는 연관관계이고 다대일의 반대는 일대다이다.

1.2 다대일 양방향

외래 키가 있는 쪽이 연관관계 주인이다. 따라서 Member.team이 연관관계의 주인이다. JPA는 외래 키를 관리할 때 연관관계의 주인만 사용한다. 주인이 아닌 Team.members는 조회를 위한 JPQL이나 객체 그래프를 탐색할 때 사용한다.

양방향 연관관계는 항상 서로를 참조하도록 개발한다. (연관관계 편의 메서드 사용)

2. 일대다

일대다 관계는 엔티티를 하나 이상 참조할 수 있으므로 자바컬렉션인 List, Set, Map 중에 하나를 사용한다.

2.1 일대다 단방향

하나의 팀은 여러 회원을 참조할 수 있는데 이런 관계를 일대다 관계라 한다.

일대다 단방향은 일대다에서 일(1)이 연관관계의 주인이다.

테이블 일대다 관계는 항상 다(N) 쪽에 외래 키가 있다. 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조이다.

일대다 단방향 관계를 매핑할 때는 @JoinColumn을 꼭 사용해야 한다. 그렇지 않으면 조인 테이블 방식을 사용한다.(중간에 테이블을 하나 추가함)

  • 일대다 단방향 매핑의 단점
    • 엔티티가 관리하는 외래 키가 다른 테이블에 있다.
    • 연관관계 관리를 위해 추가로 UPDATE SQL을 실행한다.
  • 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용

2.2 일대다 양방향

이런 매핑은 공식적으로 존재하지 않는다.

@JoinColumn(insertable=false, updatable=false)

읽기 전용 필드를 사용해서 양방향 처럼 사용하는 방법이다.

-> 다대일 양방향을 사용

3. 일대일

일대일 관계는 양쪽이 서로 하나의 관계만 가진다. (회원은 하나의 사물함만 사용하고, 사물함도 한명의 회원에 의해서만 사용된다)

  • 일대일 관계는 그 반대도 일대일
  • 주 테이블이나 대상 테이블 중에 외래 키 선택 가능
    • 주 테이블에 외래 키
    • 대상 테이블에 외래 키
  • 외래 키에 데이터베이스 유니크(UNI) 제약조건 추가

3.1 주 테이블에 외래 키

객체지향 개발자들은 주테이블에 외래 키가 있는 것을 선호한다.

JPA도 주 테이블에 외래 키가 있으면 좀 더 편하게 매핑할 수 있다.

단방향

MEMBER가 주 테이블이고 LOCKER는 대상 테이블이다.

회원은 하나의 Locker만 가지고, Locker는 하나의 회원에 의해서만 사용되는 경우이다.

@OneToOne 어노테이션을 사용하고, 이 관계는 다대일 단방향(@ManyToOne)과 거의 비슷하다.

양방향

양방향이므로 연관관계의 주인을 정해야 한다. MEMBER 테이블이 외래 키를 가지고 있으므로 Member 엔티티에 있는 Member.locker가 연관관계의 주인이다.

따라서 반대 매핑인 Locker.member는 mappedBy를 설정해준다.

3.2 대상 테이블에 외래 키

단방향

일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는 JPA에서 지원하지 않는다.

따라서 이 때는 단방향 관계를 Locker에서 Member 방향으로 수정하거나, 양방향 관계로 만들고 Locker를 연관관계의 주인으로 설정해야 한다.

양방향



 

주 엔티티인 Member 엔티티 대신에 대상 엔티티인 Locker를 연관관계의 주인으로 만들어서 LOCKER 테이블의 외래 키를 관리하도록 했다. 사실 일대일 주 테이블에 외래 키 양방향과 매핑 방법은 같다.

 

*일대일 관계 정리

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

4. 다대다

관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.

연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야한다.

하지만 객체는 컬렉션을 사용해서 객체 2개로 다대다 관계를 만들 수 있다. 예를 들어, 회원 객체는 컬렉션을 사용해서 상품들을 참조하면 되고, 반대로 상품들도 컬렉션을 사용해서 회원들을 참조하면 된다.

@ManyToMany를 사용하면 아래 그림처럼 다대다 관계를 편리하게 매핑할 수 있다.

다대다 매핑의 한계

편리해 보이지만 실무에서 사용X

예를 들어 회원이 상품을 주문하면 연결 테이블에 단순히 주문한 회원 아이디와 상품 아이디만 담고 끝나지 않는다. 보통은 열결 테이블에 주문 수량 컬럼이나 주문한 날짜 같은 컬럼이 더 필요하다.

위와 같이 연결 테이블에 주문 수량(ORDERAMOUNT)나 주문 날짜(ORDERDATE) 컬럼을 추가했다.

이렇게 컬럼을 추가하면 더는 @ManyToMany를 사용할 수 없다. 이유는 주문 엔티티나 상품 엔티티에는 추가한 컬럼들을 매핑할 수 없기 때문이다.

다대다 한계 극복

연결 테이블용 엔티티 추가(연결 테이블을 엔티티로 승격)

@ManyToMany -> @OneToMany, @ManyToOne

 

 

 

참고

자바 ORM 표준 JPA 프로그래밍(김영한)