🎫 OneToOne 연관관계
[JPA] OneToOne 연관관계 (tistory.com)
이번에는 제일 흔한 연관관계인 ManyToOne, OneToMany 연관관계이다.
이 연관관계는 1대 다 관계인 관계고 역시 FK로 연결되어 있다.
이번에는
Course와 Review 엔티티를 만들어줬다.
하나의 강의에는 여러개의 리뷰를 가진다.
강의는 1 리뷰는 多 의 연관관계가 된다.
Course
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.*;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Course {
@Id
@GeneratedValue
private Long id;
private String name;
private String author;
@OneToMany
private List<Review> reviews = new ArrayList<>();
@CreationTimestamp
private Timestamp creationTimestamp;
@UpdateTimestamp
private Timestamp updateTimestamp;
public Course(String name, String author) {
this.name = name;
this.author =author;
}
// 연관관계 편의 메소드 public void addReview(Review review) {
this.reviews.add(review);
}
@Override
public String toString() {
return String.format("Course[%s]", name);
}
}
|
cs |
Review
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import java.sql.Timestamp;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Review {
@Id
@GeneratedValue
private Long id;
private String description;
private String rating;
@ManyToOne
private Course course;
public Review(String description, String rating) {
this.description = description;
this.rating =rating;
}
@Override
public String toString() {
return String.format("Review[%s %s]",rating, description);
}
}
|
cs |
강의 > 리뷰 @OneToMany로 강의는 List<Review>를 갖는다.
리뷰 > 강의 @ManyToOne으로 리뷰는 하나의 Course를 갖는다.
실습을 위해 기본 데이터도 넣어줬다.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
insert into course(id, name, author, creation_timestamp, update_timestamp)
values(10001,'JPA in 50 Steps', '허균', CURRENT_DATE(), CURRENT_DATE());
insert into course(id, name, author, creation_timestamp, update_timestamp)
values(10002,'Spring in 50 Steps', '홍길동', CURRENT_DATE(), CURRENT_DATE());
insert into course(id, name, author, creation_timestamp, update_timestamp)
values(10003,'Spring Boot in 100 Steps', '허난설헌', CURRENT_DATE(), CURRENT_DATE());
insert into review(id,rating,description)
values(50001,'FIVE', 'Great Course');
insert into review(id,rating,description)
values(50002,'FOUR', 'Wonderful Course');
insert into review(id,rating,description)
values(50003,'FIVE', 'Awesome Course');
|
cs |
이대로 서버를 실행하면 course 테이블과 review 테이블이 각각 create되는데
@ManyToOne인 리뷰 쪽에 FK가 생성되는 것을 볼 수 있다.
그리고 특이한 것은 둘 사이의 관계 테이블이 자동으로 생성된다.
정리하자면 1 : 多 관계에서 1인 Course는 그대로 생성되고 多인 Review에는 FK로 course_id가 생성된다.
그리고 연관관계의 주인을 설정하지 않으면
둘 사이의 관계 테이블이 자동으로 생성되고 多인 Review가 uniqe 제약조건이 걸려 Course는 여러개고 Review는 하나만 들어갈 수 있게 된다.
리뷰를 insert해서 결과를 확인해본다.
CourseRepository에 특정 Course에 Review를 한번에 저장하는 간단한 메소드를 만들어줬다.
여기에 course.addReview는 미리 Course 에서 정의해둔 연관관계 편의 메소드이다.
이렇게 서버를 실행해주면
각각 테이블에 저장이 잘 된 것을 볼 수 있다.
하지만 우리가 굳이 Course와 Review를 같이 관리하는 Course_Review 테이블이 필요하진 않다.
이렇게 되면 데이터가 중복됨으로 연관관계의 주인을 설정해주는 편이 좋다.
연관관계 주인 설정
mappedBy = "연관관계의 주인"
연관관계의 주인을 Review로 정해줬다.
이대로 서버를 실행하면
Course와 Review 테이블은 똑같이 생성되지만 Course_Review 테이블은 생성되지 않는다.
그래도 위와 같이 review가 잘 저장되는 것을 볼 수 있다.
Fetch.Type
OneToMany 연관관계에서는 fetch join의 default가 LAZY이다.
ManyToOne 연관관계에서는 fetch join의 default가 EAGER이다.
풀어 말해
1 : 多 관계에서 1인 Course는 多인 Review를 LAZY하게 가져오고 多인 Review에서 1인 Course를 Eager하게 가져온다.
간단히 findById를 통해 가져온 Course를 로그로 찍어보는 테스트를 진행했다.
🛑 둘다 정의 안함 (1 = LAZY / 多 = EAGER )
둘다 정의 안한 디폴트 상황일때
OneToMany는 LAZY, ManyToOne은 EAGER fetchType을 가진다
course_id가 10003인 리뷰를 찍어보기 위해서 course를 먼저 가져오는데
course는 LAZY이기 떄문에 바로 review를 찍어낼 수 없고 각각의 테이블로 두번 select 요청을 보내 해당 course_id에 해당하는 review를 가져오게 된다.
로그도 잘 찍히는걸 볼 수 있다.
🛑 둘다 LAZY
위와 같다.
각각의 테이블에 select 요정을 보내고 로그를 찍어준다.
🛑 default의 반대 (1 = EAGER / 多 = LAZY)
하나의 Course에 딸린 Review들을 EAGER하게 가져오고 반대로는 LAZY하게 가져오도록 설정해줬다.
한번에 left outer join을 통해 course와 review를 조인해와 해당 강의의 리뷰 정보를 뿌려준다.
사실 이 경우가 하나의 select 요청만 보내기 때문에 가장 쉬워보인다.
하지만 실무에서는 가장 쓰지 말아야하는 경우이고 N+1 문제가 발생할 수 있는 경우가 된다.
이런 findById와 같은 간단한 쿼리로는 문제가 되지 않지만 jpql에서는 문제가 될 수 있다. 그래서 대부분 default로 두고 필요할 시 fetch join을 걸어 사용한다.
🛑 둘다 EAGER
위와 같다.
course 쪽에서 left outer join을 해주니 review까지 갈 필요가 없었다.
N+1와 fetch join과 관련된 내용은 다음에 정리하도록....하겠다.
코드
참고
자바 ORM 표준 JPA 프로그래밍 | 김영한 - 교보문고 (kyobobook.co.kr)
'Backend > JPA' 카테고리의 다른 글
[JPA] 상속 관계 매핑 전략 (조인 전략 / 단일 테이블 전략 / 구현 클래스마다 테이블 전략 / MappedSuperClass) (0) | 2022.12.01 |
---|---|
[JPA] ManyToMany 연관관계 (mappedby / fetchType) (0) | 2022.10.26 |
[JPA] OneToOne 연관관계 (mappedby / fetchType) (0) | 2022.10.14 |
[JPA] Entity 생성일시 수정일시 자동 입력 방법 2가지 (@CreatedDate vs @CreationTimestamp) (0) | 2022.10.04 |
[JPA] 영속성 관리 (Entity Manager / Persistence Context / 엔티티 생명주기) (2) | 2022.09.29 |
댓글