우아한테크코스/레벨3
JPA 기초: 연관관계매핑
nauni
2021. 7. 8. 08:44
수업
- A객체 → B 객체 참조 : 이동가능
- B 객체 → A 객체 참조가 없다면 : 이동불가
- 하지만 테이블은 join으로 이동가능 (항상 양방향)
- 연관관계의 주인: 객체와 객체 사이의 관계 (실제 DB와 매핑하면서 주인을 정해야 함 → 주인 대신 외래키 관리자 라고 해보자!)
연관관계: 다대일, 일대다, 양방향, 일대일
다대일 단방향
- ManyToOne : 다대일
- JoinColumn: 해도되고 안해도됨(foreign key의 이름을 지정해주는 역할) → 생략된다면
엔티티이름+id
로 지정됨 - 단방향관계
- 엔티티
Station
->Line
: 가능Line
->Station
: 불가능- 테이블
station
->line
: 가능line
->station
: 가능
- 지하철에 Line 정보추가 → 지하철입장에서는 다대일 단방향 관계 (Station→Line 가능, Line→Station 불가능)
- 👉 jpa 연관된 모든 엔티티는 영속상태여야함
- 쿼리 지식없이 JPA 사용은 불가하지!!
- 연관관계에서 지우려면 기존 연관관계를 먼저 제거하고 제거해야함!
- 양방향이 의미하는것? 객체에서!!
- OneToMany? mappedBy → 누가 fk를 들고 있지?
- Station의 어느 필드가 fk 를 가지고 있지??
@Entity
public class Line {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@OneToMany(mappedBy = "line") // Station의 line 이라는 필드가 키를 가짐
private List<Station> stations = new ArrayList<>();
}
@Entity
@Table(name = "station")
public class Station {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// ...
@ManyToOne
@JoinColumn(name = "line_id", nullable = true)
private Line line;
}
- mappedBy가 없다면 line이 키를 관리하려고 할것임 그럼 List<Station>이 10이면 Line 테이블에는 StationId가 다른 10개의 row가 생길것!!
- List<Station>: OneToMany에 mappedBy 설정이 없다면?
→ line_station 테이블인 line, station 테이블을 연결하는 연결테이블(line_station)을 생성함!! - List<Station>: OneToMany에 mappedBy 설정이 있다면?
→ line, station 테이블2개로만 관리! - mappedBy는 연결테이블 없이 관리할 수 있다고 힌트를 주는 것!!
양방향 연관관계
- target에서도 OneToOne 설정? 그럼 mappedBy로 되어있다고 알려주는것?!
- 연관관계가 어디의 필드인지 mappedBy로 알려줌
연관관계의 주인
- 각각의 입장에서는 단방향임!!!
- 객체의 입장에서
- OneToMany 단방향
- ManyToOne 단방향
- 하지만 둘은 개발자 입장에서 양방향으로 알 수 있는거지(단방향 *2)
- 키를 가지고 있는 사람 → 연관관계의 주인!
- 연관관계의 주인이라는 말보단 외래키 관리자가 와닿는다!!!!
- 외래키 관리자를 통해서만 외래키 등록, 삭제등등 이 가능
- 외래키 관리자가 아닌 것(= Line) → 읽기만 가능 (mappedBy 설정)
- 👉 보통 (1:N, N:1 상황에서) 다 쪽이 외래키를 가짐
- 👉 연관관계의 주인을 정하는 것은 키 관리자를 선택하는 것! (비지니스 중요도로 접근 하지 말자!)
- 👉 대게 다 쪽이 외래키를 가짐 = 관리자 = 주인
- 양방향 연관 관계는 결국 양쪽 다 신경 써야 한다. (조회하는 쪽 + 외래키 관리자)
station.setLine(line);
line.addStation(station);
- 한번에 양방향 관계를 설정하는 메소드를 → 연관관계 편의 메소드라고 한다.
- ⏰ 양방향 매핑 시에는 무한 루프에 빠지지 않게 조심해야 한다.
- 편한것은 리스크가 존재!
- JPA가 객체를 객체스럽게 사용하기 위함인것!!
일대다 단방향
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "member_id") // 직접 이름을 지정해준다면? (여기서 name은 연관관계가 어떤 어노테이션인지에 따라 동작방식이 다름), 여기서는 fk 추가해줌
// 여기서는 중간테이블 추가되지 않음(name을 직접 지정해준다면)
private List<Favorite> favorites = new ArrayList<>();
}
- 중간테이블 생성을 막으면서 일대다 단방향 관리가 가능
- 💥 단점이 있음
→ JPA입장에서, 외래키 관리자가 Favorite, 가지고 있니? 갖고는 있는데 잘 모르겠네..?? fk 가지고 있는지 없는지 모르겠네... 부가 쿼리가 나감
일대일 연관관계
class Station {
Long id;
String name;
LineStation lineStation;
}
class LineStation {
Long id;
}
- 반대도 일대일 연관관계
- source, target 하나 정해야함
- station 주 테이블이면 → line_station 대상테이블이 됨
- 그냥 하나 주를 정하면 다른쪽이 대상이 됨
- 사실 외래키가 어디로 가도 큰 상관이 없음.
주테이블에 외래키를 두는 경우는?
- 단방향 연관관계 생성 → 변화가 있겠지 하나의 엔티티는!
- OneToOne에서 name이 동작하는 방식? 그 테이블에 FK를 만듦
- 주테이블 입장에서: 일대일 단방향
대상테이블에 외래키를 두는 경우는?
- target엔티티에 외래키가 있다면? (1:1 → 1:N 으로 확장이 편함, 테이블 구조가 그대로 가게 됨)
- 양방향 연관관계만 성립됨(단방향으로는 기술적으로 지원하지 않음)
- 주 테이블 외래키 → source 엔티티만 조회해도 정보를 알 수 있음
다대다
- 관계형DB에서는 표현 불가~
- 그래서 그 사이 연결 테이블이 필요함 (실제 객체 세상에서는 존재하지 않음)
- 따라서 관계 이외 별다른 정보 추가 불가함
- 그래서 나중에 확장성을 고려해서라도 1:N, N:1 등으로 설정하고, Section과 같은 별도의 엔티티 객체를 두는 것이 더 좋음
- 실무에서는 거의 쓸일이 없다
- 다대다 너무 복잡하니까 가능하면 1:N, N:1 로 풀어서 사용한다!
연관관계 선택
- 객체지향관점: 일대다 단방향이 깔끔 → 부가 업데이트 쿼리가 무조건 나가게 됨.
- 기술JPA 관점: 다대일 단방향이 깔끔 → 객체의 입장에서 참조관계가 DB같음.
- 합의: 다대일 양방향 → 편의 메소드에 대한 고려를 해야함.
추가
- OneToMany Cascade 옵션 → 영속화하지 않아도 된다
- CASCADE설정은 처음부터 ALL로 하지말고, 상황에 맞춰서 추가해주는 것이 좋음!
- 고아객체
- remove하면 DB에서도 삭제될 것 같은데 관계만 끊어지고 삭제되진 않음
- orphanRemoval = true → 부모가 없는 자식은 삭제함
질문
- 엔티티에 equals와 hashcode 관련 메서드 설정해주는 것이 좋을까여?? 네 좋아요
- 실제 쿼리가 어떻게 나오는지 알 수 있어야 하기 때문에 지금은 쿼리 하나하나 확인해 보는 것이 좋음
@DataJpaTest
- 롬복 → 양방향일때 문제가 됨
참고
- 우아한테크코스 수업