LostCatBox

(JPA) JPA-Basic-CH05(다양한 연관관계 매핑)

Word count: 834Reading time: 5 min
2023/04/13 Share

연관관계 시 고려사항 3가지

  • 다중성
  • 단방향, 양방향
  • 연관관계의 주인

다중성

설계시 DB기준으로 어떻게 할지 생각해보고, 외래키를 두자, 외래키는 N 쪽에 두자!
햇갈린다면, 생각하고있던 서로의 위치를 바꿔도 적절한지 확인(다대일 -> 일대다)

  • 다대일 @ManyToOne
  • 일대다 @OneToMany
  • 일대일 @OneToOne
  • 다대다 @ManyToMany
    • 실무에서 안씀

단방향, 양방향

  • 테이블
    • 외래 키 하나로 양쪽조인 가능
    • 방향이라는 개념없음
  • 객체
    • 참조용 필드가 있는 쪽만 참조가능
    • 한쪽만 참조하면 단방향
    • 양쪽이 서로 참조하면 양방향 -> 사실은 단방향이 두개임(주인, 하인 나뉨)

연관관계의 주인

  • 테이블은 외래 키 하나로 두 테이블이 연관관계 맺음
  • 객체 양방향 관계는 참조가 2군대 있 둘중 테이블의 외래 키를 관리할 곳을 지정해야함(주인, 외래키 보유)
  • 하인(주인의 반대편): 외래키에 영향주지않음, 단순 조회

다대일

  • 외래키 N:1 중 N쪽이 가짐
  • N쪽은 연관관계 연결
  • 보편적인 방법. 가장 추천하는 방법
  • 스크린샷 2023-04-13 오전 8.26.34

일대다

  • DB에서는 외래키 N:1 중 N쪽 항상 가짐

  • 하지만 객체 연관관계의 1:N 관계 중 1이 주인임

  • 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하 는 특이한 구조

  • N쪽은 연관관계 연결

  • @JoinColumn을 꼭 사용해야 함. 그렇지 않으면 조인 테이블 방식을 사용함(중간에 테이블을 하나 추가함)

  • 스크린샷 2023-04-13 오전 8.28.54

  • 예시

    • 단점은 Team 내용을 변경했지만, 실제 DB에는 Team에서의 내용은 없고, Member의 FK을 업데이트해야하므로.. 무조건 쿼리 한번더 나감
    • 개발자입장에서 Team만 건드렸는데, Member Update 쿼리가 나가서 이상하고, 운영상에도 많은 연관관계가있을텐데…관리어려움
1
2
3
4
5
6
7
8
9
10
public class Team {
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
}

public class Member {
@ManyToOne(mappedby="members")
private Team team;
}
  • 다대일 양방향으로 만드는게 DB 설계와 비슷하고, 장점많음

일대다 단방향 정리

  • 일대다 단방향 매핑의 단점
    • 엔티티가 관리하는 외래 키가 다른 테이블에 있음
    • 연관관계 관리를 위해 추가로 UPDATE SQL 실행
  • 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자

일대다 양방향

  • 읽기 전용 만들기 전략

스크린샷 2023-04-13 오전 8.47.28

  • 이런 매핑은 공식적으로 존재X
  • @JoinColumn(insertable=false, updatable=false)
  • 읽기 전용 필드를 사용해서 양방향 처럼 사용하는 방법
  • 다대일 양방향을 사용하자
1
2
3
4
5
6
public class Member {
@ManyToOne
// 마치 주인처럼 행동하지만, insert, update 안하므로 조회전용이됨
@JoinColumn(name="TEAM_ID", insertable=false, updatable=false)
private Team team
}

일대일 관계

  • 일대일 관계는 그 반대도 일대일
  • 주 테이블이나 대상 테이블 중에 외래 키 선택 가능
  • 외래 키에 데이터베이스 유니크(UNI) 제약조건 추가

예시: 단방향 일대일

  • Member는 반드시 하나의 Locker를 갖는다.
  • 다대일(@ManyToOne) 단방향 매핑과 유사
1
2
3
4
5
6
7
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;

private String name;
}
1
2
3
4
5
6
7
@Entity
public class Member {
///...
@OneToOne
@JoinColum(name ="LOCKER_ID")
private Locker locker;
}

image-20230414071330102

예시: 양방향 일대일

  • OneToMany와 유사하게 걸어주면됨

스크린샷 2023-04-14 오전 7.19.28

1
2
3
4
5
6
7
8
9
10
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;

private String name;

@OneToOne(mappedBy = "locker")
private Member member;
}
1
2
3
4
5
6
7
@Entity
public class Member {
///...
@OneToOne
@JoinColum(name ="LOCKER_ID")
private Locker locker;
}

그렇다면 외래키를 Member(주 테이블) 또는 Locker(대상테이블)중 어떤 것이 주인(외래키 가짐)되어야할까? -> 때에 따라다르다

  • DB관점에서는
    외래키는 Locker가 갖게하는게 합리적
    추후 Member가 Locker를 여러개 들고있을수있다는 조건이 추가될 확률이 높기 때문
  • 개발자 입장에서는
    외래키는 Member가 갖게하는게 합리적
    Member는 여러곳에서 쓰이고, 이때 Locker 외래키를 가지고있다면, Locker관련 로직에서 따로 DB에 쿼리 필요없이 바로 로직을 실행할수있기때문

일대일 정리

  • 주 테이블(자주 사용 테이블)에 외래키
    • 스크린샷 2023-04-14 오전 7.19.28
    • 주 객체가 대상 객체의 참조를 가지는 것 처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾음
    • 객체지향 개발자 선호
    • JPA 매핑 편리
    • 장점: 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
    • 단점: 값이 없으면 외래 키에 null 허용
  • 대상 테이블에 외래키
    • 스크린샷 2023-04-14 오전 8.34.44
    • 대상 테이블에 외래 키가 존재
    • 전통적인 데이터베이스 개발자 선호
    • 장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
    • 단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨(프록시는 뒤에서 설명), => JPA는 프록시기능쓰면 어차피 객체를 만들때, 연관관계에 대해 조회하게되어잇기때문 => 쿼리가 다른 테이블로나가야함
    • 무조건 양방향으로 만들어야함

다대다(N:M)

실무에서 쓰지말자

  • 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없음
  • 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야함
  • 중간 테이블로 해결

스크린샷 2023-04-14 오전 8.41.21

객체는 컬렉션을 사용해서 객체 2개로 다대다 관계 가능

스크린샷 2023-04-14 오전 8.42.32

사용 방법

  • @ManyToMany
  • @JoinTable로 연결 테이블 지정
  • 다대다 매핑: 단방향, 양방향 가능

단점

  • 연결 테이블이 단순히 연결만 하고 끝나지 않음
  • 중간 테이블에 다른 정보 포함할수없음
  • 주문시간, 수량 같은 데이터가 들어올 수 있음.
    하지만, 중간 테이블에는 정보 넣을수없다.

다대다 한계 극복 => 연결 테이블용 엔티티 추가

  • 연결 테이블용 엔티티 추가(연결 테이블을 엔티티로 승격)
  • @ManyToMany -> @OneToMany, @ManyToOne

스크린샷 2023-04-14 오전 8.48.05

  • Member
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
public class Member {
@Id @GeneratedValue
private Long id;

private Long age;

private String name;

@OneToMany(mappedBy ="member")
private List<MemberProduct> memberProducts = new ArrayList<>();
}
  • Product
1
2
3
4
5
6
7
8
9
10
@Entity
public class Product {
@Id @GeneratedValue
private Long id;

private String name;

@OneToMany(mappedBy = "product")
private List<MemberProduct> memberProducts = new ArrayList<>();
}
  • MemberProduct
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity
public class MemberProduct {
@Id @GeneratedValue
private Long id;

@ManyToOne
@JoinColum(name = "MEMBER_ID")
private Member member;

@ManyToOne
@JoinColum(name = "PRODUCT_ID")
private MemberProduct memberProduct;

// 이후 필요한 값들도 추가 가능
private int count;
private int price;
private LocalDateTime orderDateTime;
}

TIP: PK값 정의

애플리케이션은 계속 확장해야하므로! 확장성 중요

PK값은 보통 두개의 의미있는 필드를 만드는 것이 아닌, 유일한 하나의 값을 추천함

왜냐하면, 의미가 들어가는 순간 중복되면서, 아예 안들어갈수도있기때문
또한 두 개의 필드가 PK라면 유연성도 떨어짐. 확장성 감소

속성 정리

@JoinColumn

스크린샷 2023-04-17 오후 8.00.34

@ManyToOne

  • 무조건 연관관계 주인이 되어야만한다.
  • mappedby 속성 없음

스크린샷 2023-04-17 오후 8.00.54

@OneToMany

스크린샷 2023-04-17 오후 8.01.15

CATALOG
  1. 1. 연관관계 시 고려사항 3가지
    1. 1.1. 다중성
    2. 1.2. 단방향, 양방향
    3. 1.3. 연관관계의 주인
  2. 2. 다대일
  3. 3. 일대다
    1. 3.1. 일대다 단방향 정리
    2. 3.2. 일대다 양방향
  4. 4. 일대일 관계
    1. 4.1. 예시: 단방향 일대일
    2. 4.2. 예시: 양방향 일대일
    3. 4.3. 그렇다면 외래키를 Member(주 테이블) 또는 Locker(대상테이블)중 어떤 것이 주인(외래키 가짐)되어야할까? -> 때에 따라다르다
    4. 4.4. 일대일 정리
  5. 5. 다대다(N:M)
    1. 5.1. 객체는 컬렉션을 사용해서 객체 2개로 다대다 관계 가능
    2. 5.2. 사용 방법
    3. 5.3. 단점
    4. 5.4. 다대다 한계 극복 => 연결 테이블용 엔티티 추가
  6. 6. 속성 정리
    1. 6.1. @JoinColumn
    2. 6.2. @ManyToOne
    3. 6.3. @OneToMany