단방향과 양방향 연관관계
테이블에서의 연관관계
- 테이블: 외래 키 하나로 양쪽 조인 가능. 방향이라는 개념이 없음.
- 객체: 참조용 필드가 있는 쪽으로만 참조 가능.
- 한쪽만 참조하면 단방향.
- 양쪽이 서로 참조하면 양방향.
연관관계의 주인
- 테이블: 외래 키 하나로 두 테이블이 연관관계를 맺음.
- 객체 양방향 관계: 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는 조인 테이블 방식을 사용하게 됩니다. 이 경우, 중간에 테이블이 하나 더 추가됩니다.
일대다 단방향 매핑의 단점:
- 외래 키 관리 위치 문제: 엔티티가 관리하는 외래 키가 다른 테이블에 있어, 연관관계 관리를 위해 추가로 UPDATE SQL을 실행해야 합니다.
- 추가적인 UPDATE SQL: 연관관계를 설정하기 위해 추가적인 UPDATE SQL이 필요합니다.
권장 사항:
- 다대일 양방향 매핑 사용 권장: 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하는 것이 더 효율적이고, 설계상으로도 적합합니다. 다대일 양방향 매핑은 외래 키를 다(N) 쪽에서 관리하며, 성능 면에서도 유리합니다.
- 일대다 단방향 매핑은 외래 키 관리가 복잡하고 추가적인 UPDATE SQL 실행으로 인해 성능 저하가 발생할 수 있습니다.
- 다대일 양방향 매핑은 외래 키를 다(N) 쪽에서 관리하여, 성능 및 유지보수 측면에서 유리합니다.
- 객체지향적인 설계를 위해 연관관계 편의 메소드를 사용하여 관계를 명확히 관리할 수 있습니다.
일대일 관계 매핑
일대일 관계는 한 엔티티가 다른 엔티티와 정확히 하나의 연관을 가지는 경우를 의미합니다. 일대일 관계는 주로 두 가지 방식으로 매핑할 수 있습니다:
- 주 테이블에 외래 키를 두는 방식
- 대상 테이블에 외래 키를 두는 방식
외래 키에 유니크 제약 조건을 추가하여 일대일 관계를 보장합니다.
일대일 관계 정리
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 관계에서 사용되어 관계를 설정하는 필드에 붙여줍니다.
주요 속성 및 설명
- name
- 설명: 매핑할 외래 키의 이름을 지정합니다.
- 기본값: 필드명 + '_' + 참조하는 테이블의 기본 키 컬럼명.
- 예시: @JoinColumn(name = "TEAM_ID")
- referencedColumnName
- 설명: 외래 키가 참조하는 대상 테이블의 컬럼명을 지정합니다.
- 기본값: 참조하는 테이블의 기본 키 컬럼명.
- 예시: @JoinColumn(name = "TEAM_ID", referencedColumnName = "ID")
- foreignKey
- 설명: 외래 키 제약조건을 직접 지정할 수 있습니다. 이 속성은 테이블을 생성할 때만 사용됩니다.
- 기본값: 없음 (DDL 생성 시 추가할 제약조건을 지정할 수 있음).
- 기타 속성
- unique: 고유 제약조건을 설정합니다. (기본값: false)
- nullable: null 허용 여부를 설정합니다. (기본값: true)
- insertable: 삽입 가능 여부를 설정합니다. (기본값: true)
- updatable: 수정 가능 여부를 설정합니다. (기본값: true)
- columnDefinition: DDL 생성 시 사용될 SQL 조각을 직접 설정할 수 있습니다. (기본값: 없음)
- table: 매핑할 테이블을 지정합니다. (기본값: 엔티티가 매핑된 테이블)
@ManyToOne - 주요 속성
@ManyToOne 어노테이션은 JPA에서 엔티티 간의 다대일(N:1) 관계를 매핑할 때 사용됩니다. 이 어노테이션은 @JoinColumn과 함께 사용되어 외래 키 관계를 설정합니다.
주요 속성 및 설명
- optional
- 설명: false로 설정하면 연관된 엔티티가 항상 존재해야 합니다. 즉, null을 허용하지 않습니다.
- 기본값: TRUE
- 예시: @ManyToOne(optional = false)
- fetch
- 설명: 글로벌 페치 전략을 설정합니다. 페치 전략에는 즉시 로딩(EAGER)과 지연 로딩(LAZY)이 있습니다.
- 기본값: @ManyToOne의 경우 FetchType.EAGER, @OneToMany의 경우 FetchType.LAZY
- 예시: @ManyToOne(fetch = FetchType.LAZY)
- cascade
- 설명: 영속성 전이 기능을 사용합니다. 이 속성을 통해 연관된 엔티티에 대한 영속성 작업(PERSIST, MERGE, REMOVE 등)을 전이시킬 수 있습니다.
- 예시: @ManyToOne(cascade = CascadeType.ALL)
- targetEntity
- 설명: 연관된 엔티티의 타입 정보를 설정합니다. 이 기능은 거의 사용하지 않으며, 컬렉션을 사용할 때 제네릭으로 타입 정보를 알 수 있습니다.
- 기본값: 없음
- 예시: @ManyToOne(targetEntity = Team.class)
@OneToMany - 주요 속성
@OneToMany 어노테이션은 JPA에서 엔티티 간의 일대다(1: N) 관계를 매핑할 때 사용됩니다. 이 어노테이션은 주로 반대편 엔티티에 @ManyToOne과 함께 사용되어 일대다 관계를 설정합니다.
주요 속성 및 설명
- mappedBy
- 설명: 연관관계의 주인 필드를 선택합니다. mappedBy 속성은 연관관계의 주인이 아닌 쪽에 설정되며, 주인이 되는 엔티티의 필드명을 지정합니다.
- 기본값: 없음
- 예시: @OneToMany(mappedBy = "team")
- fetch
- 설명: 글로벌 페치 전략을 설정합니다. 페치 전략에는 즉시 로딩(FetchType.EAGER)과 지연 로딩(FetchType.LAZY)이 있습니다.
- 기본값: @OneToMany의 경우 FetchType.LAZY
- 예시: @OneToMany(fetch = FetchType.EAGER)
- cascade
- 설명: 영속성 전이 기능을 사용합니다. 이 속성을 통해 연관된 엔티티에 대한 영속성 작업(PERSIST, MERGE, REMOVE 등)을 전이시킬 수 있습니다.
- 예시: @OneToMany(cascade = CascadeType.ALL)
- 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 |