오봉이와 함께하는 개발 블로그
JPA - 연관관계 매핑 1:1, N:M 본문
1:1[일대일] 주 테이블에 외래키 단방향
- 1:1 관계는 그 반대도 1:1 관계이다.
- 1:1 관계는 주 테이블이나 대상 테이블 중 외래키 선택이 가능하다.
- 주 테이블에 외래키
- 대상 테이블에 외래키
- DB 입장에서 외래키에 유니크 제약조건을 추가해야 1:1 관계가 성립된다.
MEMBER, LOCKER 모두 서로 하나씩만 가질 수 있으며, MEMBER 테이블에 LOCKER_ID를 외래키로 넣고 유니크 제약 조건을 달거나, LOCKER 테이블에 MEMBER_ID를 외래키로 넣고 유니크 제약 조건을 달아도 1:1 관계가 된다.
MEMBER 테이블이 주 테이블인 상황이다.
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
정리
- N:1(@ManyToOne) 단방향 매핑과 유사
1:1[일대일] 주 테이블에 외래키 양방향
N:1 양방향 매핑과 유사하게 외래키가 있는 곳이 연관관계의 주인이 된다.
물론 반대편은 mappedBy 적용이 필요하다.
// Member.java
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
// Locker.java
@OneToOne(mappedBy = "locker")
private Member member;
정리
- N:1 양방향 매핑 처럼 외래키가 있는 곳이 연관관계의 주인
- 반대편은 mappedBy 적용
1:1[일대일] 대상 테이블에 외래키 단방향
1:N 단방향과 유사한 상황으로 Member에 locker 객체가 있는데 LOCKER 테이블에 MEMBER_ID가 외래키로 들어가 있는 상황이다.
이건 JPA 스팩에서 지원하지 않는 방법이고 편법을 쓸 수도 없는 방법이다.
정리
- JPA에서 지원하지 않을 뿐더러 편법조차 없다.
- 단, 양방향 관계는 지원한다.
1:1[일대일] 대상 테이블에 외래키 양방향
그림을 보면 알 수 있듯 사실 Member 엔티티가 MEMBER 테이블을 관리하는 모양이 된다.
Member 엔티티의 locker 객체는 읽기 전용으로 만들면 된다.
정리
- 사실 1:1 주 테이블에 외래키 양방향과 매핑 방법은 같다.
1:1 관계 총 정리
주 테이블에 외래키 단방향을 사용해도, 대상 테이블에 외래키 양방향을 사용해도 1:1 관계는 유효하다.
대상 테이블에 외래키 양방향을 사용하다가 추후 LOCKER 테이블에서 여러 MEMBER_ID를 가질 수 있게 규칙이 바뀌었을 때 유니크 제약조건만 제거하면 된다는 장점이 있다.
자연스럽게 1:1에서 1:N으로 바뀌는 것이다.
그런데, 주 테이블에 외래키 단방향을 사용해서 하나의 MEMBER테이블에 여러 LOCKER_ID를 가질 수 있게 규칙이 바뀐다면 변경할 부분들이 많아진다.
하지만 하나의 LOCKER가 여러 MEMBER를 가질 수 있게 비즈니스가 바뀐다면 이 선택이 옳게 되는 것이다.
개발자 입장에서는 MEMBER에 LOCKER가 있는 주 테이블에 외래키 단방향이 여러가지 성능상 이점 외에도 여러가지 장점이 있다.
예를 들면 MEMBER 테이블 하나를 select 해도 값의 유무, 어떤 값이 있는지 까지 알 수 있다.
너무 먼 미래를 생각하지 않고 명확한 1:1 관계면 주 테이블에 외래키 단방향을 가지고 있는 구조를 선택해도 좋고, 작업자의 선호에 따라 대상 테이블에 외래키 양방향을 가지고 있는 구조를 선택해도 좋다.
정리
- 주 테이블에 외래키
- 주 객체가 대상 객체의 참조를 가지듯 주 테이블에 외래키를 두고 대상 테이블을 찾는다
- 객체지향 개발자가 선호
- JPA 매핑 편리
- 장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
- 단점 : 값이 없으면 외래키에 null 허용
- 대상 테이블에 외래키
- 대상 테이블에 외래키가 존재(null이 없다)
- 전통적인 DB 개발자가 선호
- 장점 : 주 테이블과 대상 테이블을 1:1에서 1:N 관계로 변경할 때 테이블 구조 유지, null 안 들어감
- 단점 : 양방향으로 만들어야 함, JPA가 지원하는 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩된다.
- 어차피 LOCKER 테이블을 쿼리해봐야 하기 때문에 의미가 없어짐.
N:M[다대다]
실무에서는 사용을 지양해야 한다.
N:M 구조는 관계형 데이터베이스는 정규화된 테이블 두 개로 N:M 관계를 표현할 수 없기 때문에 중간에 연결 테이블을 추가해서 1:N, N:1 관계로 풀어내야 한다.
하지만 객체는 컬렉션(List<Member>, List<Product>)을 사용해서 N:M 관계가 가능하다.
N:M 매핑은 단방향, 양방향 모두 지원하며 @ManyToMany를 사용하고, @JoinTable로 연결 테이블을 지정할 수 있다.
단방향
// Member
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT")
private List<Product> products = new ArrayList<>();
양방향
// Member
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT")
private List<Product> products = new ArrayList<>();
// Product
@ManyToMany(mappedBy = "products")
private List<Member> members = new ArrayList<>();
한계
- 편리해 보이지만 실무에서 사용하면 안 된다.
- 연결 테이블이 단순히 연결만 하고 끝나는 것이 아니기 때문
- 주문시간, 수량 같은 추가 데이터(컬럼)가 들어가야 하는데, 그것이 불가능하다.
극복
- 연결 테이블용 엔티티를 추가한다.
- 연결 테이블이 엔티티로 승격되는 구조
- @ManyToMany가 @OneToMany, @ManyToOne 구조로 바뀜.
// Member
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts = new ArrayList<>();
// MemberProduct
@ManyToOne
@JoinColumn("MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn("PRODUCT_ID")
private Product product;
// Product
@OneToMany(mappedBy = "product")
private List<MemberProduct> memberProducts = new ArrayList<>();
출처 : 인프런 김영한 지식공유자님의 스프링 부트와 JPA 실무 완전 정복 로드맵 강의
'BE > JPA' 카테고리의 다른 글
JPA - 상속관계 매핑 (0) | 2022.06.28 |
---|---|
JPA - 실전 예제 3 - 다양한 연관관계 매핑 (0) | 2022.06.27 |
JPA - 연관관계 매핑 N:1, 1:N (0) | 2022.06.26 |
JPA - 실전 예제 2 - 연관관계 매핑 (0) | 2022.06.25 |
JPA - 양방향 연관관계와 연관관계의 주인 - 주의점, 정리 (0) | 2022.06.25 |