본문 바로가기

JPA

[JPA] JPA 연관관계 매핑 (2)

 

 

 

단방향과 양방향 연관관계

테이블에서의 연관관계

  • 테이블: 외래 키 하나로 양쪽 조인 가능. 방향이라는 개념이 없음.
  • 객체: 참조용 필드가 있는 쪽으로만 참조 가능.
    • 한쪽만 참조하면 단방향.
    • 양쪽이 서로 참조하면 양방향.

연관관계의 주인

  • 테이블: 외래 키 하나로 두 테이블이 연관관계를 맺음.
  • 객체 양방향 관계: A -> B, B -> A처럼 참조가 2군데.
  • 객체 양방향 관계: 참조가 2군데 있음. 둘 중 테이블의 외래 키를 관리할 곳을 지정해야 함.
  • 연관관계의 주인: 외래 키를 관리하는 참조.
  • 주인의 반대편: 외래 키에 영향을 주지 않음, 단순 조회만 가능.

 

다대일 [N:1]

다대일 관계에서는 여러 엔티티가 하나의 엔티티와 연관됩니다. 예를 들어, 여러 Member가 하나의 Team에 속하는 경우입니다.

- 가장 많이 사용하는 연관관계

- 다대일의 반대는 일대다

다대일(N:1) 관계 예제

Member

import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
}

 

Team

import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    // 연관관계 편의 메소드
    public void addMember(Member member) {
        members.add(member);
        member.setTeam(this);
    }
}

 

 

일대다 [1:N]

일대다 관계를 매핑할 때 주의해야 할 점은 일대다 단방향 매핑은 권장하지 않는다는 것입니다. 왜냐하면, 외래 키가 다 쪽에 위치하기 때문에 일대다 단방향 매핑은 효율적이지 않기 때문입니다. 대신, 다대일 양방향 매핑을 사용하는 것이 더 일반적입니다.

 

Team

import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany
    @JoinColumn(name = "team_id") // Member 테이블의 team_id FK
    private List<Member> members = new ArrayList<>();
}

 

Member

import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}

 

일대다 단방향 정리

일대다 단방향 매핑의 특징:

  • 일대다 단방향에서 일(1)이 연관관계의 주인: 객체 관계에서 '일(1)'이 연관관계를 주도합니다.
  • 테이블 일대다 관계는 항상 다(N) 쪽에 외래 키가 있음: 관계형 데이터베이스에서 외래 키는 항상 '다(N)'쪽 테이블에 위치합니다.
  • 객체와 테이블의 차이: 객체 모델과 테이블 모델의 차이로 인해, 반대편 테이블의 외래 키를 관리하는 특이한 구조가 됩니다.
  • @JoinColumn 사용 필수: @JoinColumn 어노테이션을 사용하여 외래 키를 지정해야 합니다. 그렇지 않으면, JPA는 조인 테이블 방식을 사용하게 됩니다. 이 경우, 중간에 테이블이 하나 더 추가됩니다.

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

  1. 외래 키 관리 위치 문제: 엔티티가 관리하는 외래 키가 다른 테이블에 있어, 연관관계 관리를 위해 추가로 UPDATE SQL을 실행해야 합니다.
  2. 추가적인 UPDATE SQL: 연관관계를 설정하기 위해 추가적인 UPDATE SQL이 필요합니다.

권장 사항:

  • 다대일 양방향 매핑 사용 권장: 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하는 것이 더 효율적이고, 설계상으로도 적합합니다. 다대일 양방향 매핑은 외래 키를 다(N) 쪽에서 관리하며, 성능 면에서도 유리합니다.

 

  • 일대다 단방향 매핑은 외래 키 관리가 복잡하고 추가적인 UPDATE SQL 실행으로 인해 성능 저하가 발생할 수 있습니다.
  • 다대일 양방향 매핑은 외래 키를 다(N) 쪽에서 관리하여, 성능 및 유지보수 측면에서 유리합니다.
  • 객체지향적인 설계를 위해 연관관계 편의 메소드를 사용하여 관계를 명확히 관리할 수 있습니다.

 

일대일 관계 매핑

일대일 관계는 한 엔티티가 다른 엔티티와 정확히 하나의 연관을 가지는 경우를 의미합니다. 일대일 관계는 주로 두 가지 방식으로 매핑할 수 있습니다:

  1. 주 테이블에 외래 키를 두는 방식
  2. 대상 테이블에 외래 키를 두는 방식

외래 키에 유니크 제약 조건을 추가하여 일대일 관계를 보장합니다.

 

일대일 관계 정리

1. 주 테이블에 외래 키 양방향 매핑

  • 외래 키가 있는 곳이 연관관계의 주인: 다대일 양방향 매핑처럼 외래 키가 있는 곳이 연관관계의 주인입니다.
  • 반대편은 mappedBy 적용: 외래 키를 가지고 있지 않은 쪽은 mappedBy를 사용합니다.

Member.java (주 테이블)

import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToOne(mappedBy = "member", cascade = CascadeType.ALL)
    private Locker locker;

    public void setLocker(Locker locker) {
        this.locker = locker;
        locker.setMember(this);
    }
}

 

 

Locker.java (대상 테이블)

import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class Locker {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToOne
    @JoinColumn(name = "member_id", unique = true)
    private Member member;
}

 

2. 대상 테이블에 외래 키 단방향 정리

  • 단방향 관계는 JPA 지원X: JPA에서는 단방향 관계를 지원하지 않습니다.
  • 양방향 관계는 지원: 양방향 관계는 지원합니다.

3. 일대일 정리

주 테이블에 외래 키

  • 주 객체가 대상 객체의 참조를 가지는 것처럼: 주 테이블에 외래 키를 두고 대상 테이블을 참조합니다.
  • 객체지향 개발자 선호: 객체 지향적으로 설계할 때 더 자연스러움.
  • JPA 매핑 편리: JPA 매핑이 더 편리합니다.
  • 장점: 주 테이블만 조회해도 대상 테이블의 데이터가 있는지 확인 가능.
  • 단점: 값이 없으면 외래 키에 null을 허용해야 합니다.

대상 테이블에 외래 키

  • 대상 테이블에 외래 키가 존재: 외래 키를 대상 테이블에 둡니다.
  • 전통적인 데이터베이스 개발자 선호: 전통적인 DB 설계에서는 더 선호.
  • 장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조를 유지할 수 있습니다.
  • 단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됩니다.

 

다대다 매핑

1. 다대다 관계의 한계

  • 관계형 데이터베이스에서 정규화된 테이블 두 개로 다대다 관계를 표현할 수 없다.
  • 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야 한다.
  • 연결 테이블이 단순히 연결만 하고 끝나지 않기 때문이다. 연결 테이블에 주문 시간, 수량 같은 추가 데이터를 포함할 수 있다.

2. JPA에서의 다대다 매핑

  • @ManyToMany 사용: JPA에서 다대다 관계를 설정할 때 사용.
  • @JoinTable로 연결 테이블 지정: 연결 테이블을 명시적으로 지정.

3. 다대다 매핑의 한계

  • 실무에서 사용X: 편리해 보이지만 실무에서 사용하지 않는다.
  • 연결 테이블에 추가 데이터: 연결 테이블이 단순히 연결만 하지 않고 추가 데이터를 포함할 수 있기 때문이다.

Member

import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToMany
    @JoinTable(name = "member_product",
            joinColumns = @JoinColumn(name = "member_id"),
            inverseJoinColumns = @JoinColumn(name = "product_id"))
    private List<Product> products = new ArrayList<>();
}

 

Product

import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "products")
    private List<Member> members = new ArrayList<>();
}

 

다대다 매핑의 대안: 일대다, 다대일 관계로 풀어내기

실무에서는 다대다 매핑 대신 연결 테이블을 추가하여 일대다와 다대일 관계로 풀어내는 방식이 더 많이 사용됩니다. 예제 코드를 통해 어떻게 구현하는지 살펴보겠습니다.

 

Member

import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberProducts = new ArrayList<>();
}

 

Prduct

import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "product")
    private List<MemberProduct> memberProducts = new ArrayList<>();
}

 

MemberProduct

import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class MemberProduct {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

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

    private int orderAmount;
}

 

 

@JoinColumn 어노테이션

@JoinColumn 어노테이션은 JPA에서 엔티티 간의 연관 관계를 맺을 때 외래 키(Foreign Key)를 매핑할 때 사용됩니다. 이 어노테이션은 보통 @ManyToOne, @OneToOne 관계에서 사용되어 관계를 설정하는 필드에 붙여줍니다.

주요 속성 및 설명

  1. name
    • 설명: 매핑할 외래 키의 이름을 지정합니다.
    • 기본값: 필드명 + '_' + 참조하는 테이블의 기본 키 컬럼명.
    • 예시: @JoinColumn(name = "TEAM_ID")
  2. referencedColumnName
    • 설명: 외래 키가 참조하는 대상 테이블의 컬럼명을 지정합니다.
    • 기본값: 참조하는 테이블의 기본 키 컬럼명.
    • 예시: @JoinColumn(name = "TEAM_ID", referencedColumnName = "ID")
  3. foreignKey
    • 설명: 외래 키 제약조건을 직접 지정할 수 있습니다. 이 속성은 테이블을 생성할 때만 사용됩니다.
    • 기본값: 없음 (DDL 생성 시 추가할 제약조건을 지정할 수 있음).
  4. 기타 속성
    • unique: 고유 제약조건을 설정합니다. (기본값: false)
    • nullable: null 허용 여부를 설정합니다. (기본값: true)
    • insertable: 삽입 가능 여부를 설정합니다. (기본값: true)
    • updatable: 수정 가능 여부를 설정합니다. (기본값: true)
    • columnDefinition: DDL 생성 시 사용될 SQL 조각을 직접 설정할 수 있습니다. (기본값: 없음)
    • table: 매핑할 테이블을 지정합니다. (기본값: 엔티티가 매핑된 테이블)

 

@ManyToOne - 주요 속성

@ManyToOne 어노테이션은 JPA에서 엔티티 간의 다대일(N:1) 관계를 매핑할 때 사용됩니다. 이 어노테이션은 @JoinColumn과 함께 사용되어 외래 키 관계를 설정합니다.

주요 속성 및 설명

  1. optional
    • 설명: false로 설정하면 연관된 엔티티가 항상 존재해야 합니다. 즉, null을 허용하지 않습니다.
    • 기본값: TRUE
    • 예시: @ManyToOne(optional = false)
  2. fetch
    • 설명: 글로벌 페치 전략을 설정합니다. 페치 전략에는 즉시 로딩(EAGER)과 지연 로딩(LAZY)이 있습니다.
    • 기본값: @ManyToOne의 경우 FetchType.EAGER, @OneToMany의 경우 FetchType.LAZY
    • 예시: @ManyToOne(fetch = FetchType.LAZY)
  3. cascade
    • 설명: 영속성 전이 기능을 사용합니다. 이 속성을 통해 연관된 엔티티에 대한 영속성 작업(PERSIST, MERGE, REMOVE 등)을 전이시킬 수 있습니다.
    • 예시: @ManyToOne(cascade = CascadeType.ALL)
  4. targetEntity
    • 설명: 연관된 엔티티의 타입 정보를 설정합니다. 이 기능은 거의 사용하지 않으며, 컬렉션을 사용할 때 제네릭으로 타입 정보를 알 수 있습니다.
    • 기본값: 없음
    • 예시: @ManyToOne(targetEntity = Team.class)

 

 

@OneToMany - 주요 속성

@OneToMany 어노테이션은 JPA에서 엔티티 간의 일대다(1: N) 관계를 매핑할 때 사용됩니다. 이 어노테이션은 주로 반대편 엔티티에 @ManyToOne과 함께 사용되어 일대다 관계를 설정합니다.

주요 속성 및 설명

  1. mappedBy
    • 설명: 연관관계의 주인 필드를 선택합니다. mappedBy 속성은 연관관계의 주인이 아닌 쪽에 설정되며, 주인이 되는 엔티티의 필드명을 지정합니다.
    • 기본값: 없음
    • 예시: @OneToMany(mappedBy = "team")
  2. fetch
    • 설명: 글로벌 페치 전략을 설정합니다. 페치 전략에는 즉시 로딩(FetchType.EAGER)과 지연 로딩(FetchType.LAZY)이 있습니다.
    • 기본값: @OneToMany의 경우 FetchType.LAZY
    • 예시: @OneToMany(fetch = FetchType.EAGER)
  3. cascade
    • 설명: 영속성 전이 기능을 사용합니다. 이 속성을 통해 연관된 엔티티에 대한 영속성 작업(PERSIST, MERGE, REMOVE 등)을 전이시킬 수 있습니다.
    • 예시: @OneToMany(cascade = CascadeType.ALL)
  4. targetEntity
    • 설명: 연관된 엔티티의 타입 정보를 설정합니다. 이 기능은 거의 사용하지 않으며, 컬렉션을 사용할 때 제네릭으로 타입 정보를 알 수 있습니다.
    • 기본값: 없음
    • 예시: @OneToMany(targetEntity = Member.class)

'JPA' 카테고리의 다른 글

[JPA] JPA 프록시와 연관관계 관리  (0) 2024.06.30
[JPA] JPA 고급 매핑  (0) 2024.06.29
[JPA] JPA 연관관계 매핑 (1)  (0) 2024.06.29
[JPA] JPA 엔티티 매핑  (0) 2024.06.29
[JPA] JPA 영속성 관리  (0) 2024.06.29