DB/자바 ORM 표준 JPA 프로그래밍

[6장] 다양한 연관관계 매핑

꾸준함. 2025. 2. 27. 00:34

다대일

  • 다대일 관계의 반대 방향은 항상 일대다 관계
  • 마찬가지로 일대다 관계의 반대 방향은 항상 다대일 관계
  • 데이터베이스 테이블의 1, 다 관계에서 외래 키는 항상 다쪽에 존재 (연관관계의 주인)

 

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

회원 엔티티와 팀 엔티티 코드를 통해 다대일 단방향 연관관계를 알아보겠습니다.

  • 회원은 Member.team으로 팀 엔티티를 참조할 수 있지만
  • 팀에는 회원을 참조하는 필드가 없기 때문에 다대일 단방향 연관관계
  • @JoinColumn(name = "TEAM_ID")를 사용해서 Member.team 필드를 TEAM_ID 외래 키와 매핑했기 때문에 Member.team 필드로 회원 테이블의 TEAM_ID 외래키를 관리함

 

 

https://lar542.github.io/JPA/2019-09-01-JPA1/

 

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

다대일 양방향의 객체 연관관계에서 Member.team이 연관관계의 주인이고 Team.members은 연관관계의 주인이 아닙니다.

  • 다쪽인 MEMBER 테이블이 왜래 키를 가지고 있으므로, Member.team이 연관관계의 주인
  • JPA는 외래 키를 관리할 때 연관관계의 주인만 사용
  • 주인이 아닌 Team.members는 조회를 위한 JPQL이나 객체 그래프를 탐색할 때 사용

 

 

https://lar542.github.io/JPA/2019-09-01-JPA1/

 

부연 설명

  • 양방향 연관관계는 항상 서로 참조해야 하며 어느 한쪽만 참조할 경우 양방향 연관관계가 성립하지 않음
    • 항상 서로 참조하게 하려면 회원의 setTeam(), 팀의 addMember() 메서드와 같은 연관관계 편의 메서드를 작성하는 것이 좋음
    • 편의 메서드는 한 곳에만 작성하거나 양쪽 다 작성할 수 있는데, 양쪽에 다 작성할 경우 무한 루프에 빠지지 않도록 주의 필요

 

일대다

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

 

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

  • 하나의 팀은 여러 회원을 참조할 수 있는데 이런 관계를 일대다 관계라 함
  • 팀은 회원들을 참조하지만 반대로 회원은 팀을 참조하지 않을 경우 둘의 관계는 단방향
  • 일대다 단방향 관계는 특이하게도 팀 엔티티의 Team.members로 회원 테이블의 TEAM_ID 외래키를 관리함
    • 보통 자신이 매핑한 테이블의 외래 키를 관리하는데
    • 해당 매핑은 반대쪽 테이블에 있는 외래 키를 관리
    • 이는 일대다 관계에서 외래 키는 항상 다쪽 테이블에 있는데 다 쪽인 Member 엔티티에는 외래 키를 매핑할 수 있는 참조 필드가 없기 때문

 

 

https://lar542.github.io/JPA/2019-09-01-JPA2/

 

부연 설명

  • 일대다 단방향 관계를 매핑할 때는 반드시 @JoinColumn을 명시해야 함
    • 명시하지 않을 경우 JPA는 연결 테이블을 중간에 두고 연관관계를 관리하는 조인 테이블 전략을 기본으로 사용해서 매핑함

 

1.1 일대다 단방향 매핑의 단점

  • 일대다 단방향 매핑의 단점은 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있다는 점
  • 본인 테이블에 외래 키가 있을 경우 엔티티의 저장과 연관관계 처리르 INSERT SQL 한 번으로 끝낼 수 있지만, 다른 테이블에 외래 키가 있을 경우 연관관계 처리를 위해 UPDATE SQL을 추가로 실행해야 함

 

 

 

부연 설명

  • 연관관계에 대한 정보는 Team 엔티티의 members가 관리하고 Member 엔티티는 Team 엔티티에 대한 참조가 없기 때문에 Team 엔티티를 저장할 때 Team.members의 참조 값을 확인해서 회원 테이블에 있는 TEAM_ID 외래 키를 업데이트함

 

1.2 일대다 단방향 매핑보다는 다대일 양방향 매핑을 권장

  • 일대다 단방향 매핑을 사용하면 엔티티를 매핑한 테이블이 아닌 다른 테이블의 외래 키를 관리해야 하기 때문에 성능과 관리 측면에서 모두 열위
    • 위 문제를 해결하려면 일대다 단방향 매핑 대신 다대일 양방향 매핑을 사용하는 것
    • 다대일 양방향 매핑은 관리해야 하는 외래 키가 본인 테이블에 있으므로 관리가 편하고 성능도 좋음

 

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

  • 일대다 양방향 매핑은 존재하지 않음 
  • 대신 다대일 양방향 매핑을 사용해야 함
    • 일대다 양방향과 다대일 양방향은 사실 똑같은 말이지만 책에서는 왼쪽을 연관관계의 주인으로 가정해서 분류하고 있음
    • 정확히 말하자면 관계형 데이터베이스의 특성 일대다, 다대일 관계는 항상 다 쪽에 외래키가 있으므로 양방향 매핑에서 @OneToMany는 연관관계의 주인이 될 수 없음

 

  • 일대다 양방향 매핑이 완전히 불가능한 것은 아님
    • 일대다 단방향 매핑 반대편에 같은 외래 키를 사용하는 다대일 단방향 매핑을 읽기 전용으로 하나 추가하면 됨
    • 하지만 이 방법 또한 일대다 단방향 매핑이 가지는 단점을 그대로 가지므로 웬만하면 다대일 양방향 매핑을 사용하는 것을 권장

https://lar542.github.io/JPA/2019-09-01-JPA2/

 

 

일대일 [1:1]

  • 일대일 관계는 양쪽이 서로 하나의 관계만 가지며 다음과 같은 특징을 가지고 있음
    • 일대일 관계는 그 반대도 일대일 관계
    • 테이블 관계에서 일대다, 다대일은 항상 다 쪽이 외래 키를 가지는 반면 일대일 관계는 주 테이블이나 대상 테이블 둘 중 어느 곳이나 외래 키를 가질 수 있음

 

  • 일대일 관계는 주 테이블이나 대상 테이블 중 누가 외래 키를 가질지 선택해야 함
    • 주 테이블에 외래 키: 주 테이블이 외래 키를 가지고 있으므로 주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있음
    • 대상 테이블에 외래 키: 테이블 관계를 일대일에서 일대다로 변경할 때 테이블 구조를 그대로 유지 가능한 장점 있음

 

1. 주 테이블에 외래 키

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

 

1.1 주 테이블에 단방향 외래 키

회원과 사물함의 일대일 단방향 관계는 다음과 같습니다.

  • MEMBER가 주 테이블
  • LOCKER는 대상 테이블

 

https://lar542.github.io/JPA/2019-09-01-JPA3/

 

 

부연 설명

  • 일대일 관계이므로 객체 매핑에 @OneToOne을 사용했고 DB에는 LOCKER_ID 외래 키에 UNIQUE 제약 조건을 추가함
  • 해당 관계는 다대일 단방향(@ManyToOne)과 거의 비슷함

 

1.2 주 테이블에 양방향 외래 키

회원과 사물함의 일대일 양방향 관계는 다음과 같습니다.

 

https://lar542.github.io/JPA/2019-09-01-JPA3/

 

 

부연 설명

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

 

2. 대상 테이블에 외래 키

 

2.1 대상 테이블에 단방향 외래 키

  • 일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는 JPA에서 지원하지 않으며 이런 모양으로 매핑할 수 있는 방법도 없음
  • 따라서 양방향 관계로 만든 뒤 Locker를 연관관계의 주인으로 설정해야 함

 

https://lar542.github.io/JPA/2019-09-01-JPA3/

 

2.2 대상 테이블에 양방향 외래 키

  • 일대일 매핑에서 대상 테이블에 외래 키를 두고 싶으면 아래와 같이 양방향으로 매핑해야 함
  • 주 엔티티인 Member 엔티티 대신 대상 엔티티인 Locker를 연관관계의 주인으로 만들어서 LOCKER 테이블의 외래 키를 관리해야 함

 

https://lar542.github.io/JPA/2019-09-01-JPA3/

 

 

부연 설명

  • proxy를 사용할 때 외래 키를 직접 관리하지 않는 일대일 관계는 지연 로딩으로 설정해도 즉시 로딩됨
    • Locker.member는 지연 로딩 가능하지만 Member.locker는 지연 로딩으로 설정해도 즉시 로딩됨
    • proxy의 한계 때문에 발생하는 문제인데 프록시 대신 bytecode instrumentation을 사용하면 해결 가능
    • https://vladmihalcea.com/hibernate-lazytoone-annotation/
 

Hibernate LazyToOne annotation - Vlad Mihalcea

Learn how the Hibernate LazyToOne annotation works and why you should use NO_PROXY lazy loading with bytecode enhancement.

vladmihalcea.com

 

다대다 [N:M]

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

 

https://escapefromcoding.tistory.com/402

 

 

  • 반면, 객체는 테이블과 다르게 객체 두 개로 다대다 관계를 만들 수 있음
  • @ManyToMany를 사용하면 다음과 같이 다대다 관계를 편리하게 매핑 가능

 

https://escapefromcoding.tistory.com/402

 

1. 다대다: 단방향

 

 

 

부연 설명

  • 회원 엔티티와 상품 엔티티를 @ManyToMany로 매핑
  • 중요한 포인트는 @ManyToMany와 @JoinTable을 통해 연결 테이블을 매핑한 것
    • 이에 따라 회원과 상품을 연결하는 Member_Product 엔티티 없이 매핑을 완료할 수 있음
    • MEMBER_PRODUCT 테이블은 다대다 관계를 일대다, 다대일 관계로 풀어내기 위해 필요한 연결 테이블일 뿐이기 때문에 다대다 관계를 사용할 때 크게 신경 쓰지 않아도 됨

 

  • 연결 테이블을 매핑하는 @JoinTable의 속성은 다음과 같음
    • name: 연결 테이블을 지정
    • joinColumns: 현재 방향인 회원과 매핑할 조인 컬럼 정보 지정
    • inverseJoinColumns: 반대 방향인 상품과 매핑할 조인 컬럼 정보 지정

 

 

1.1 다대다 관계 저장

 

 

부연 설명

  • 회원1과 상품A의 연관관계를 설정했으므로 회원1을 저장할 때 연결 테이블에도 값이 저장됨

 

 

1.2 다대다 관계 탐색

 

 

 

2. 다대다: 양방향

  • 양방향 연관관계 매핑을 위해 역방향에도 @ManyToMany를 사용
  • 양쪽 중 원하는 곳에 mappedBy로 연관관계의 주인을 지정
    • mappedBy가 없는 쪽이 연관관계의 주인

 

 

 

  • 다대다의 양방향 연관관계를 설정하는 연관관계 편의 메서드는 다음과 같음

 

 

  • 양방향 연관관계로 만들었으므로 product.getMembers()를 사용해서 역방향으로 객체 그래프를 탐색할 수 있음

 

 

 

3. 다대다: 매핑의 한계와 극복, 연결 엔티티 사용

  • @ManyToMany를 사용하면 연결 테이블을 자동으로 처리해 주기 때문에 도메인 모델이 단순해지고 여러 가지로 편리하다는 장점이 있지만 해당 매핑을 실무에서 사용하기에는 한계가 있음
    • ex) 회원이 상품을 주문하면 연결 테이블에 단순히 주문한 회원 아이디와 상품 아이디만 담고 끝나는 것이 아니고 보통은 연결 테이블에 주문 수량 컬럼이나 주문한 날짜 같은 컬럼이 더 필요함
    • 하지만 이렇게 컬럼을 추가할 경우 주문 엔티티나 상품 엔티티에는 추가한 컬럼들을 매핑할 수 없기 때문에 더는 @ManyToMany를 사용할 수 없음
    • 결국 다음과 같이 연결 테이블을 매핑하는 연결 엔티티를 생성하고 이곳에 추가한 컬럼들을 매핑해야 함
    • 또한 엔티티 간의 관계도 테이블 관계처럼 다대다가 아니라 일대다, 다대일 관계로 풀어야 함


https://escapefromcoding.tistory.com/402

 

부연 설명

  • 회원과 회원상품을 양방향 관계로 생성했으며 회원상품 엔티티 쪽이 외래키를 가지고 있으므로 연관관계의 주인
  • 상품 엔티티에서 회원상품 엔티티로 객체 그래프 탐색 기능이 필요하지 않다고 판단해서 연관관계를 만들지 않음
  • 회원상품 엔티티를 보면 기본 키를 매핑하는 @Id와 외래 키를 매핑하는 @JoinColumn을 동시에 사용해서 기본 키 + 외래 키를 한 번에 매핑했고 @IdClass를 사용해서 복합 기본 키를 매핑함

 

3.1 복합 기본 키

  • 회원 상품 엔티티는 기본 키가 MEMBER_ID와 PRODUCT_ID로 이루어진 복합 기본 키
  • JPA에서 복합 키를 사용하려면 별도의 식별자 클래스를 생성해야 하고 엔티티에 @IdClas를 사용해서 식별자 클래스를 지정하면 됨
  • 복합 키를 위한 식별자 클래스 (i.g. MemberProductId)는 다음과 같은 특징이 있음
    • 복합 키는 별도의 식별자 클래스로 생성해야 함
    • Serializable을 구현해야 함
    • equals와 hashCode 메서드를 구현해야 함
    • 기본 생성자가 있어야 함
    • 식별자 클래스는 public이어야 함
    • @IdClass를 사용하는 방법 외 @EmbeddedId를 사용하는 방법도 있음

 

3.2 식별 관계

  • 회원상품은 회원과 상품의 기본 키를 받아서 자신의 기본 키로 사용
    • 이렇게 부모 테이블의 기본 키를 받아서 자신의 기본 키 + 외래 키로 사용하는 것을 DB 용어로 식별 관계 (Identifying Relationship)이라고 부름

 

3.3 다대다 매핑으로 구성한 관계 저장하는 예

  • 회원상품 엔티티를 생성하면서 연관된 회원 엔티티와 상품 엔티티를 설정했음
  • 회원상품 엔티티는 DB에 저장될 때 회원의 식별자와 상품의 식별자를 가져와서 자신의 기본 키 값으로 사용


 

3.4 다대다 매핑으로 구성한 관계 조회하는 예

  • 복합 키는 항상 식별자 클래스를 생성해야 함
  • 아래 코드에서 em.find()를 보면 생성한 식별자 클래스로 엔티티를 조회함


 

4. 다대다: 새로운 기본 키 사용

  • 복합 키를 사용하면 ORM 매핑에서 처리할 일이 상당히 많아지므로 추천하는 기본 키 생성 전략은 데이터베이스에서 자동으로 생성해 주는 대리 키를 Long 값으로 사용하는 것
    • 해당 방법의 장점은 간편하고 거의 영구히 쓸 수 있으며 비즈니스에 의존하지 않는다는 점
    • 또한, ORM 매핑 시에 복합 키를 만들지 않아도 되므로 간단히 매핑을 완성할 수 있음


https://escapefromcoding.tistory.com/402

 

4.1 새로운 기본 키 사용하며 저장하고 조회하는 예제

  • 식별자 클래스를 사용하지 않아서 코드가 한결 단순해짐


 

5. 다대다 연관관계 정리

  • 다대다 관계를 일대다 다대일 관계로 풀어내기 위해 연결 테이블을 생성할 때 식별자를 어떻게 구성할지 선택해야 함
    • 식별 관계: 받아온 식별자를 기본 키 + 외래 키로 사용
    • 비식별 관계: 받아온 식별자는 외래 키로만 사용하고 새로운 식별자를 추가

 

  • 데이터베이스 설계에서는 전자처럼 부모 테이블의 기본 키를 받아서 자식 테이블의 기본 키 + 외래 키로 사용하는 것을 식별 관계라 함
  • 후자처럼 단순히 외래 키로만 사용하는 것을 비식별 관계라 함
    • 객체 입장에서 보면 비식별 관계를 사용하는 것이 복합 키를 위한 식별자 클래스를 변도로 생성하지 않아도 됨에 따라 단순하고 편리하게 ORM 매핑을 할 수 있음
    • 이런 이유로 식별관계보다는 비식별 관계를 추천

 

참고

자바 ORM 표준 JPA 프로그래밍 - 김영한 저

반응형

'DB > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글

[5장] 연관관계 매핑 기초  (1) 2025.02.24
[4장] 엔티티 매핑  (0) 2025.02.21
[3장] 영속성 관리  (0) 2025.02.21
[2장] JPA 시작  (1) 2025.02.21
[1장] JPA 소개  (0) 2025.02.18