LostCatBox

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

Word count: 834Reading time: 5 min
2023/04/13 11 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가지
  2. 2. 다대일
  3. 3. 일대다
  4. 4. 일대일 관계
  5. 5. 다대다(N:M)
  6. 6. 속성 정리