본문 바로가기

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

06. 다양한 연관관계 매핑

💡 본 게시글은 김영한님의 인프런(Inflearn) 강의 자바 ORM 표준 JPA 프로그래밍 - 기본편에 대해 공부하고, 정리한 내용입니다.


1. 연관관계 매핑 시 고려사항

JPA를 사용하여 객체와 테이블 간의 연관관계를 매핑할 때는 몇 가지 중요한 사항을 고려해야 합니다. 이러한 고려사항은 올바른 설계와 최적의 성능을 보장합니다.

1) 연관관계 매핑 시 고려해야 할 3가지

  1. 다중성: 연관관계의 수(다대일, 일대다, 일대일, 다대다)를 의미합니다.
  2. 단방향, 양방향: 객체 관계의 방향성을 정의합니다.
  3. 연관관계의 주인: 외래 키(Foreign Key)를 관리하는 주체를 결정합니다.

(1) 다중성

다중성은 연관된 엔티티 간의 관계가 몇 대 몇인지를 정의합니다. JPA에서 지원하는 다중성 매핑 어노테이션은 다음과 같습니다:

  • 다대일(N:1): @ManyToOne
  • 일대다(1:N): @OneToMany
  • 일대일(1:1): @OneToOne
  • 다대다(N:M): @ManyToMany
  • Note: 대칭적 관점에서 바라보면 이해가 쉬워집니다. 예를 들어, 일대일(1:1)의 반대는 일대일(1:1), 다대다(N:M)의 반대도 다대다(N:M)입니다. 다대일(N:1)의 반대는 일대다(1:N), 일대다(1:N)의 반대는 다대일(N:1)입니다.

(2) 단방향, 양방향

테이블 연관관계

  • 테이블: 외래 키 하나로 양쪽 조인이 가능합니다. 즉, 방향이라는 개념이 없습니다.

객체 연관관계

  • 객체: 참조용 필드가 있는 쪽에서만 참조가 가능합니다.
    • 한쪽만 참조하면 단방향.
    • 양쪽이 서로 참조하면 양방향입니다. (사실상 단방향이 2개 있는 것과 같음)

(3) 연관관계의 주인

  • 테이블: 외래 키 하나로 두 테이블 간의 연관관계를 설정할 수 있습니다.
  • 객체: 양방향 관계에서는 A -> B, B -> A 처럼 서로 참조하는 두 가지 관계가 존재합니다. 이 중 한 곳에서 외래 키를 관리해야 합니다.
  • 연관관계의 주인: 외래 키를 관리하는 주체입니다. 주인의 반대편은 외래 키에 영향을 주지 않고, 단순 조회만 가능합니다.

2) 다중성에 따른 매핑 전략

(1) 다대일 (N:1)

가장 일반적으로 사용하는 연관관계입니다.

다대일 단방향 매핑

  • 설계: MemberTeam을 참조하고, TeamMember를 알지 못하는 구조입니다.

Member 엔티티 코드:

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

    @Column(name = "USERNAME")
    private String username;

    @ManyToOne // 다대일 관계 설정
    @JoinColumn(name = "TEAM_ID") // 외래 키 매핑
    private Team team;
}
  • 다대일 단방향 매핑 정리:
    • 가장 많이 사용하는 연관관계입니다.
    • 다대일의 반대는 일대다입니다.

다대일 양방향 매핑

  • 외래 키가 있는 쪽이 연관관계의 주인입니다. 양쪽이 서로 참조하도록 개발합니다.

Team 엔티티 코드:

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

    private String name;

    @OneToMany(mappedBy = "team") // 역방향 매핑
    private List<Member> members = new ArrayList<>();
}

(2) 일대다 (1:N)

JPA 표준 스펙에서 지원은 하지만 실무에서는 권장되지 않는 모델입니다.

일대다 단방향 매핑

  • 설계: TeamMember들을 알고 있지만, MemberTeam을 모르는 구조입니다.

Team 엔티티 코드:

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

    private String name;

    @OneToMany
    @JoinColumn(name = "TEAM_ID") // FK 매핑
    private List<Member> members = new ArrayList<>();
}

Member 엔티티 코드:

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

    @Column(name = "USERNAME")
    private String username;
}
  • 문제점: TeamList<Member> 값을 바꾸면 Member 테이블에 추가적인 UPDATE 쿼리가 발생합니다.

일대다 양방향 매핑

  • Member에서 Team으로 조회하고자 하면 약간의 변칙적인 방법을 사용합니다.

Member 엔티티 코드:

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

    @Column(name = "USERNAME")
    private String username;

    @ManyToOne
    @JoinColumn(insertable = false, updatable = false) // 읽기 전용
    private Team team;
}

(3) 일대일 (1:1)

일대일: 주 테이블에 외래 키 (단방향)

  • 설계: Member 엔티티가 외래 키를 가지는 구조입니다.

Member 엔티티 코드:

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

    @Column(name = "USERNAME")
    private String username;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID") // 외래 키 매핑
    private Locker locker;
}

일대일: 대상 테이블에 외래 키 (단방향)

  • 반대쪽 사이드에 외래 키를 두는 경우를 말합니다. 단방향 관계는 JPA에서 지원하지 않습니다.

일대일 정리

  • 주 테이블에 외래 키: 객체 지향 개발자 선호, 조회 시 장점
  • 대상 테이블에 외래 키: 전통적인 데이터베이스 개발자 선호, 테이블 구조 변경 시 유리

(4) 다대다 (N:M)

다대다는 실무에서 사용하지 않는 것이 좋습니다.

  • 제한 사항: 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없습니다.
  • 해결책: 연결(중간) 테이블을 추가하여 일대다, 다대일 관계로 풀어내야 합니다.

Member 엔티티 코드:

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

    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT") // 연결 테이블 매핑
    private List<Product> products = new ArrayList<>();
}

Product 엔티티 코드:

@Entity
public class Product {
    @Id 
    @GeneratedValue
    private Long id;

    private String name;
}
  • 다대다 매핑의 한계:
    • 매핑 정보 외에 추가적인 데이터를 넣을 수 없습니다.
    • 중간 테이블이 숨겨져 있어 예측하기 어려운 쿼리가 발생할 수 있습니다.
  • 해결책: 연결 테이블용 엔티티를 추가하여 @ManyToMany@OneToMany, @ManyToOne으로 변환합니다.

MemberProduct 엔티티 코드:

@Entity
public class MemberProduct {
    @Id 
    @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;

    private int count;
    private int price;

    private LocalDateTime orderDatetime;
}

3) **연관관계의 주인을 정하는 기준**

  • 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안 됩니다.
  • 연관관계의 주인은 실제 테이블의 외래 키 위치를 기준으로 결정해야 합니다.

4) 총정리

  1. 연관관계 매핑의 핵심은 다중성, 단방향/양방향, 연관관계의 주인을 이해하고 설계하는 것입니다.
  2. 단방향 매핑으로 시작하고, 필요할 때 양방향 매핑을 추가합니다.
  3. 연관관계의 주인은 외래 키가 있는 곳으로 설정하며, 비즈니스 로직과 상관없이 테이블 구조를 기준으로 결정합니다.
  4. 다대다 매핑은 실무에서 사용하지 않으며, 연결 테이블을 엔티티로 승격하여 풀어내야 합니다.
  5. 무한 루프와 같은 문제를 방지하기 위해 엔티티를 직접 반환하지 말고, DTO로 변환하여 반환합니다.

이러한 원칙들을 적용하면 JPA를 통한 안정적이고 확장 가능한 애플리케이션을 개발할 수 있습니다.

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

05. 연관관계 매핑 기초  (0) 2024.09.08
04. 엔티티 매핑  (1) 2024.09.08
03. 영속성 관리 - 내부 동작 방식  (0) 2024.08.11
02. JPA 시작하기  (0) 2024.08.11
01. JPA 소개  (0) 2024.07.24