Backend/JPA

[JPA] ManyToOne, OneToMany 연관관계 (mappedby / fetchType)

비전공자 기록광 2022. 10. 18. 17:21
반응형

🎫 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과 관련된 내용은 다음에 정리하도록....하겠다.

 


코드

 

TIL/Spring_Boot/jpa_study/src/main/java/com/udemy/jpa_study/relationships at main · recordbuffer/TIL · GitHub

 

GitHub - recordbuffer/TIL: Today I Learned

Today I Learned. Contribute to recordbuffer/TIL development by creating an account on GitHub.

github.com

참고

https://www.udemy.com/share/101XoW3@elgoPx8G1T8cav5XKpLRW3l1YMiA0G9QVjj9LVY4DZHANcC8MBEdqpgFoppJBXcag==/

 
 

 

자바 ORM 표준 JPA 프로그래밍 | 김영한 - 교보문고 (kyobobook.co.kr)

 

자바 ORM 표준 JPA 프로그래밍 | 김영한 - 교보문고

자바 ORM 표준 JPA 프로그래밍 | 자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA

product.kyobobook.co.kr

 

반응형