일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Java
- 자료형
- 연산자
- datatype
- Kotlin
- JavaScript
- g1gc
- Fluent-bit
- quicksort
- zgc
- jvm
- Spring Security
- While
- redis
- MergeSort
- IAC
- Algorithm
- JPA
- UserDetails
- For
- lambda
- ansible
- C++
- datastructure
- Class
- Sprint Security
- If
- SpringBoot Initializr
- 기초
- programmers
- Today
- Total
뭐라도 끄적이는 BLOG
JPA 06.02 - 다양한 연관관계 매핑 [ 1 : 1, N : M ] 본문
일대일
일대일 관계는 주 테이블이나 대상 테이블 중에 어떤 테이블이던지 외래 키를 가질수 있습니다. 어느 테이블에 외래 키가 들어갈지 선택했다면 해당 외래 키에 데이터베이스 유니크(UNI)제약조건 추가되어야 일대일관계가 됩니다.(굳이 넣지 않아도 되지만 넣지않으면 애플리케이션에서 관리를 아주 잘해야합니다.)
단방향
회원이 사물함을 단 하나 가지고 있다고 생각하겠습니다. 이경우 MEMBER 테이블에 LOCKER_ID를 UNI로 넣어도 되고 반대로 LOCKER테이블에 MEMBER_ID를 UNI로 넣어도 됩니다. 어느경우든 일대일 연관관계가 됩니다.
위 예시는 MEMBER테이블에 LOCKER_ID를 넣고 연관관계를 매핑하 였습니다. MEMBER가 주 테이블이 되고 LOCKER는 대상 테이블이 됩니다. 모양만 본다면 다대일 단방향과 다른건 거의 없습니다. 단지 @ManyToOne 어노테이션이 @OneToOne 어노테이션으로 바꼈을 뿐입니다.
@Entity
@Getter
@Setter
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
Locker locker;
}
@Entity
@Getter
@Setter
public class Locker {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "LOCKER_ID")
private Long id;
private String name;
}
양방향
양방향이라고 해도 달라질건 별로 없습니다. Locker엔티티에 member만 추가해주고 테이블에 따라 다대일 처럼 mappedBy를 설정해주면 됩니다.
@Entity
@Getter
@Setter
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
Locker locker;
}
@Entity
@Getter
@Setter
public class Locker {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "LOCKER_ID")
private Long id;
private String name;
@OneToOne(mappedBy = "locker")
private Member member;
}
다대일 양방향 매핑 처럼 외래 키가 있는 곳이 연관관계의 주인이 됩니다.
대상 테이블에 외래키 단방향 / 양방향
Member의 locker가 연관관계의 주인이 되고 싶은데 실제 테이블에서는 LOCKER테이블이 MEMBER_ID가 있습니다. 이러한 대상 테이블에 외래 키가 있는 단방향 관계는 JPA에서 지원하지 않습니다.
하지만 양방향관계는 만들수 있습니다. 연관관계의 주인을 매핑할때 Locker의 member를 주인으로 하면 됩니다. 사실 이방법은 기존 일대일 양방향과 다르지 않습니다.
정리
일대일 관계에서 단방향과 양방향은 크게 어렵지 않습니다. 일대일 관계에서 논란이 되는것은 어느쪽에 외래키를 두어야 하는가 입니다. 둘중에 어느곳에 외래키를 두든 일대일 관계는 성립하지만 미래에 DB를 변경해야 될 시점까지 생각하게 된다면 어디에 두어야할지 조금더 생각해 보게 됩니다.
만일 한 사람이 LOCKER를 여러개 가질수 있게 변경된다면 LOCKER에 외래키를 두는것이 좋을 수 있습니다. LOCKER의 MEMBER_ID의 유니크 제약조건만 해제하면 다대일로 바로 사용할 수 있기 때문입니다. MEMBER에 외래키가 있다면 변경 포인트가 많아지게 될것입니다.
하지만 LOCEKR를 여러 MEMBER가 사용할수있도록 변경된다면? MEMBER에 외래키가 있는것이 더 좋을것입니다.
코드를 작성하는 입장에서는 Member엔티티에 locker가 있는것이 유리할 수 있습니다. 대부분의 비즈니스 로직에서MEMBER테이블과 같은 사용자 정보를 많이 가져오게 될 것인데 LOCKER 테이블에 대한 Join없이 Locker가 있는지 없는지 알 수 있기 때문입니다.
결국 그냥 둘중 선호하는 방식대로 만들면 됩니다.
외래키를 어느곳에 넣을지 각각을 정리하자면 아래와 같습니다.
주(많이 엑세스하는) 테이블 | 대상 테이블 외래키 |
- 주 객체가 대상 객체의 참조를 가지는 것 처럼 주 테이블에 외래키를 두고 대상 테이블을 찾음 - 객체지향 개발자 선호 - JPA 매핑 편리 |
- 대상 테이블에 외래 키가 존재 - 전동적인 데이터베이스 개발자 선호 - 일대일 |
장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인가능 | 장점 : 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지 |
단점 : 값이 없으면 외래 키에 null 허용 | 단점 : 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉 시 로딩됨 |
N : M
관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없습니다. 보통 다대다 관계를 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용하게 됩니다.
객체는 컬렉션을 사용해서 객체 2개로 다대다 관계가 가능합니다.
@ManyToMany를 사용할 수 있고 @JoinTable로 연결 테이블을 지정할 수 있습니다.
...
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT")
private List<Product> products = new ArrayList<>();
...
단순히 단방향 관계를 만들고 싶다면 주 테이블 엔티티에 위와 같은 코드를 사용하면 됩니다. 그리고 생성된 테이블을 보면 MEMBER_PRODUCT 중간테이블이 생성되며 MEMBER의 PK와 PRODUCT의 PK가 외래키로 들어가며 동시에 이 두개의 값이 MEMBER_PRODUCT의 PK가 됩니다.
...
@ManyToMany(mappedBy = "products")
private List<Member> members = new ArrayList<>();
...
양방향 관계를 만들고 싶을때 대상테이블에 위와 같은 코드를 사용하면 됩니다.
결론적으론 다대다 매핑이 편리해 보이지만 실제로 사용하면 안됩니다. 실제로 데이터베이스에서 다대다 매핑을 위해 연결 테이블을 많이 사용하게 되는데 이는 단순히 연결만 하고 끝나지 않습니다. 주문시간, 수량 같은 데이터가 들어올 수 있습니다.
하지만 JPA에서 생성해주는 다대다의 중간 테이블은 단순히 매핑 정보만 들어가고 부가정보를 넣을 수 없습니다. 그리고 중간테이블이 숨겨져 있기 때문에 생각지도 못한 쿼리가 생성될 수 있습니다. 이러한 한계가 있기 때문에 @ManyToMany를 사용하기보단 데이터베이스 테이블과 같이 중간에 연결용 엔티티를 추가하고 @OneToMany, @ManyToOne으로 풀어가는게 좋습니다.
※ 참고 자료
'Java > JPA' 카테고리의 다른 글
RDA와 Object oriented 비교 (0) | 2023.07.17 |
---|---|
JPA 소개 (0) | 2023.07.17 |
JPA 06.01 - 다양한 연관관계 매핑 [ N : 1, 1 : N ] (0) | 2021.04.17 |
JPA 05.02 - 양방향 연관관계와 연관관계의 주인 (0) | 2021.04.16 |
JPA 05.01 - 연관관계 매핑, 단방향 연관관계 (0) | 2021.04.16 |