🟩 엔티티 연관관계 매핑 고려할 3가지
01. 다중성
연관관계에는 다중성이 있다.
다중성은 엔티티 간의 관계에서 한 엔티티가 다른 엔티티와 어떤 관계를 맺는지에 대한 개념이다.
01-01. 다중성 종류
- 다대일 (N:1, @ManyToOne)
- 일대다 (1:N, @OneToMany)
- 일대일 (1:1, @OneToONe)
- 다대다 (N:M, @ManyToMany)
이 중에서 다대일, 일대다 관계를 가장 많이 사용한다.
다대다 관계의 경우는 잘 사용하지 않는 편이다.
02. 단방향, 양방향
테이블은 외래키 하나로 양방향으로 조회가 가능하다.
때문에 테이블에는 방향이라는 개념이 없다.
하지만 객체에서는 참조용 필드를 가지고 있다.
객체는 참조용 필드를 가진 객체만이 연관된 객체를 조회할 수 있기 때문에 방향을 가질 수 있다.
03. 연관관계의 주인
데이터베이스에서는 외래키 하나로 두 테이블이 연관관계를 맺는다.
테이블의 연관관계를 관리하는 포인트가 바로 외래키이다.
두 엔티티가 양방향 관계라면 객체의 연관관계를 관리하는 포인트가 두 곳이다.
이럴 경우 연관관계의 주인을 정해서 어느쪽에서 관리할지 결정해야 한다.
이번에는 객체와 다른 객체가 어떤 관계를 맺을지에 대한 "다중성"에 대해서 알아보자.
🟩 다대일 (N:1, @ManyToOne)
- 다대일(N:1)에서의 연관관계 주인은 "다(N)"쪽이다.
01. 상품, 카테고리 다대일 상황 예시
- 하나의 상품은 단 하나의 카테고리에 속할 수 있다.
- 여러 상품은 하나의 카테고리에 속할 수 있다.
02. 상품, 카테고리 다대일 단방향 매핑 코드
- 상품 객체는 카테고리 객체를 필드로 가지고 있다.
- 카테고리 객체는 상품에 대한 정보를 알지 못한다.
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private title;
@ManyToOne
@JoinColumn(name = "category_id")
private Category category;
}
@Entity
public class Category {
@Id @GeneratedValue
private Long id;
private String title;
}
상품과 카테고리에 @ManyToOne, @JoinColumn를 이용해서 다대일 단방향으로 나타낼 수 있었다.
03. 상품, 카테고리 다대일 양방향 매핑 코드
- 상품 객체는 카테고리 객체를 필드로 가지고 있다.
- 카테고리 객체는 상품 컬렉션을 필드로 가지고 있다.
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private title;
@ManyToOne
@JoinColumn(name = "category_id")
private Category category;
}
카테고리에 상품 컬렉션을 필드로 두고 연관관계 주인을 Product로 두었다.
@Entity
public class Category {
@Id @GeneratedValue
private Long id;
private String title;
@OneToMany(mappedBy = "category")
private List<Product> products = new ArrayList<>();
}
🟩 일대다 (1:N, @OneToMany)
- 다대일 관계의 반대 방향이다.
- 일대다 관계는 엔티티를 하나 이상 참조할 수 있기에 자바 컬렉션을 이용해야 한다.
01. 상품, 카테고리 일대다 상황 예시
- 하나의 상품은 단 하나의 카테고리에 속할 수 있다.
- 여러 상품은 하나의 카테고리에 속할 수 있다.
02. 상품, 카테고리 일대다 단방향 매핑 코드
- 일대다 매핑은 Category.products로 상품 테이블의 외래키(category_id)를 관리한다.
- 일대다 매핑은 반대쪽 테이블에 있는 외래키를 관리하는 것을 볼 수 있다.
@Entity
public class Category {
@Id @GeneratedValue
private Long id;
private String title;
@OneToMany
@JoinColumn(name = "product_id")
private List<Product> products = new ArrayList<>();
}
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private title;
}
02-01. 일대다 단방향 관계 매핑시에 @JoinColumn을 명시해야 한다.
- @JoinColumn을 명시하지 않을 시에 JPA는 "조인 테이블" 전략을 기본으로 사용하게 된다.
02-02. 일대다 단방향 관계 매핑 단점
- 매핑한 객체가 관리하는 외래키가 다른 테이블에 있다는 점
- INSERT SQL을 한 후에 연관관계 처리를 위해 UPDATE SQL을 추가로 호출해야 한다.
02-03. 일대다 단방향 매핑보다 다대일 양방향 매핑을 권장한다.
일대다 단방향 관계를 매핑하게 될 경우 성능뿐만 아니라 개발하는데 헷갈릴 수도 있다.
여러 문제가 발생할 가능성이 많으니 조회가 필요할 경우 "다대일 양방향 매핑"을 이용하는 편이 좋을 것이다.
03. 상품, 카테고리 일대다 양방향 매핑 코드
- 일대다 양방향 매핑은 존재하지 않으므로 다대일 양방향 매핑에서 옵션을 바꿔서 처리해야 한다.
@Entity
public class Category {
@Id @GeneratedValue
private Long id;
private String title;
@OneToMany
@JoinColumn(name = "product_id")
private List<Product> products = new ArrayList<>();
}
상품에 카테고리 엔티티를 수정하거나 삽인한다고 해도 데이터베이스에 영향이 가지 않고 읽기만 가능하게 만들었다.
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private title;
@ManyToOne
// 옵션을 추가한다.
@JoinColumn(name = "category_id", insertable = false, updatable = false)
private Category category;
}
🟩 일대일 (1:1, @OneToOne)
- 일대일 관계는 주 테이블, 대상 테이블 둘 중 어느곳이서든 외래키를 가질 수 있다.
01. 주 테이블에 외래키를 추가할 경우
- 주 테이블만 확인해도 대상 테이블과 연관관계를 알 수 있다.
- 좀 더 편리하게 매핑할 수 있다.
01-01. 일대일 단반향 매핑 코드
- 회원이 단 하나의 장바구니를 갖는 상황
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String ndame;
@OneToOne
@JoinColumn(name = "cart_id")
private Cart cart;
}
@Entity
public class Cart {
@Id @GeneratedValue
private Long id;
}
01-02. 일대일 양방향 매핑 코드
- 양방향 관계일 경우 연관관계 주인을 정해야 한다.
- MEMBER가 외래키를 가지고 있으므로 연관관계의 주인으로 한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "cart_id")
private Cart cart;
}
@Entity
public class Cart {
@Id @GeneratedValue
private Long id;
@OneToOne(mappedBy = "cart")
private Member member;
}
02. 대상 테이블에 외래키를 추가할 경우
02-01. 일대일 단방향 매핑 코드
일대일 관계에서 대상 테이블에 외래키가 있는 경우를 JPA에서 지원하지 않는다.
이러한 경우 단방향 관계를 수정하거나 양방향 관계로 만들어야 한다.
@Entity
public class Cart {
@Id @GeneratedValue
private Long id;
@OneToOne
@JoinColumn(name = "member_id")
private Member member;
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@OneToOne(mappedBy = "member")
private Cart cart;
}
🟩 다대다 (N:N, @ManyToMany)
- 관계형 데이터베이스에서 다대다 관계를 표현할 수 없다.
- 이러한 이유 때문에 @ManyToMany + @JoinTable을 이용해서 두 엔티티로 해결하곤 한다.
- 하지만 @ManyToMany를 이용하지 않고 조인 테이블을 만들어서 해결하는 방법이 더 확장성 있는 방법이다.
여기서는 @ManyToMany를 사용하지 않고 곧바로 더 확장성 있는 방법을 사용하겠다.
01. 대리키와 두 테이블의 식별자값을 이용해서 중간 테이블을 만들자.
- 기본키 자동 생성 전략을 이용해서 대리키를 만든다.
- 회원과 상품의 기본키를 외래키로 받는다.
02. 조인 테이블 엔티티도 만들어 사용하자.
- 회원_상품에서 필요한 새로운 필드를 추가하기 용이하다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts = new ArrayList<>();
}
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private String title;
}
@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 Integer quantity;
}
🔗 참고