매핑 시 사용하는 EAGER과 LAZY.
둘이 무슨 차인데?
지난 블로깅에서 1:1, 1:N, N:1 (단방향/양방향) 매핑에 대해서 알아 보았다.
https://kcode-recording.tistory.com/345
[JPA] N:1, 1:N, 1:1, M:N 연관관계 매핑 (단방향/양방향 연관관계)
객체와 테이블 매핑 이해하기 JPA에서 테이블을 한 개만 사용하는 것이 아니라면 필요한 것은 연관관계 매핑이다. 객체 지향 프로그래밍에서는 객체간 상태를 쉽게 참조하고 호출해 상호작용이
kcode-recording.tistory.com
추가적으로 매핑 시 사용하는 fetchType에 대해서 이야기 하고자 하는데,
fetchType이 무엇이고, 어떤것이 있으며, 어떻게 사용하는지 차근히 알아보자.
FetchType이란?
FetchType은 객체간의 매핑을 위한 ORM 기술인 JPA가 하나의 Entity를 조회할 때,
연관관계가 있는 객체들을 어떻게 가져올지를 나타내는(열거형 Enum 타입) 설정값을 말한다.
FetchType에는 어떤 것이 있을까?
이는 EAGER과 LAZY 두가지가 존재한다.
1. EAGER(즉시 로딩)
연관된 Entity를 즉시 로딩하는 것으로 A와 B Entity가 연관관계라면, A를 조회 할 때 연관된 B를 모두 로딩하게 된다.
즉, A를 조회하는 시점에 B까지 불러오는 쿼리를 날려 한번에 데이터를 불러오게 되는 것이다.
이는 성능 이슈를 발생시킬 수 있다는 단점이 있다.
2. LAZY(지연 로딩)
이는 EAGER과 반대이다.
는 즉시 로딩하는 것이 아닌 실제 사용을 할 때 로딩을 하게 된다.
즉, Member를 조회할 때 쿼리문을 한 번 날리고, Team을 사용하는 시점에 쿼리문을 한번 더 날리게 된다.
A를 조회할 때에는 B를 로딩하지 않고, B가 필요한 시점에 로딩을 하게 되기 때문에
Entity가 많고 사용되지 않을 때 유용하게 사용이 가능하다.
그럼 언제 어떻게 무엇을 사용해야 하는 것일까?
언제나 여러개가 존재하는 이유는 그만한 이유가 있기 때문일 것이다.
언제 어떤것을 사용하는지 알아보기 이전에 이 둘의 차이점은 무엇일까를 더 자세히 보고자 한다.
위에서 이야기 한 것처럼 둘의 차이점은 A를 조회 시 B를 함께 가져오는지, 사용시 가져오는지인데,
이를 들여다 보았을 때에는 어떤 차이가 또 존재할까?
우선 LAZY(지연 로딩)의 경우,
A 엔티티를 조회하고 B 객체의 클래스를 확인해보면 Proxy 객체가 조회 된다.
그 다음 B를 조회한다면 조회 시에 쿼리문이 나가고 B는 실제 객체가 되어 있는 것을 확인할 수 있다.
그렇다면 EAGER은 객체 생성이 어떻게 될까?
한 번에 조회하기 때문에 쿼리문을 한 번에 날리고,
A를 조회하더라도 B 클래스를 확인해보면 Proxy가 아닌 실제 객체이다.
그렇다면 어떤 것을 사용하는 것이 좋을까?
이는 연관관계를 어떻게 사용하는지에 따라 다르다.
만약 대부분의 비즈니스 로직에서 A와 B가 매번 같이 쓰인다면?
LAZY를 사용하여 굳이 쿼리문을 2번 날려 따로 조회할 필요가 있을까? 이 때 EAGER을 사용하면 되는 것이다.
그렇다면 LAZY는 언제 사용할까?
EAGER의 경우 위와 같은 편리함은 존재하지만, 다양한 단점들이 존재한다.
이를 위해 LAZY가 존재하며, 실무에서는 LAZY 전략을 많이 사용한다.
1. 예상하지 못한 SQL이 발생
만약 @ManyToOne 매핑이 10개 있는데 이가 모두 EAGER로 설정되어 있다면?
조인이 10개 일어나는 것이며, 실무에서는 이보다 더 많을 수 있다. 그렇기에 실무에서는 가급적 자연 로딩을 권장한다고 한다.
2. JPQL에서 N+1 문제 발생
복잡한 쿼리를 풀어낼 때 JPQL을 많이 사용한다.
em.find()는 PK를 정해놓고 DB에서 가져와 JPA 내부에서 최적화 할 수 있지만
JPQL에서는 입력받은 쿼리string을 그대로 SQL로 반환한다.
LAZY라면 프록시를 넣지만, EAGER의 경우 반환 시점에 다 조회가 되어 있어야 하기 때문에
1개의 쿼리를 날렸을 때 N개의 쿼리문이 나갈 수 있게 된다.
FetchType의 EAGER과 LAZY에 대해서 알아 보았는데,
결론은 되도록 실무에서는 LAZY 전략을 가져가는 것이 좋은데,
만약 A와 B를 함께 사용하는 경우에도 LAZY를 통해 쿼리를 두 번 날려야 할까?를 보면
JPQL의 fetch join을 통해 해당 시점에 한번의 쿼리로 가져와서 사용할 수 있다.
이 뿐만 아니라 Entity 그래프와 애너테이션으로 풀거나 배치 사이즈 설정으로도 해결이 가능하다.
@ManyToOne, @OneToOne과 같이 @XXXToOne으로 끝나는 애너테이션은 기본이 EAGER(즉시 로딩)이며,
@OneToMany, @ManyToMany는 기본이 LAZY(지연 로딩)이므로,
EAGER의 경우 명시적으로 LAZY로 설정하여 사용하는 것이 좋다.
'공부 자료 > Spring' 카테고리의 다른 글
| [JPA] N:1, 1:N, 1:1, M:N 연관관계 매핑 (단방향/양방향 연관관계) (1) | 2023.11.27 |
|---|---|
| [Spring + JPA] JPA Entity 매핑 (feat. Entity 만들기) (2) | 2023.11.23 |
| [Spring] @AllArgsConstructor, @NoArgsConstructor를 지양하자? (0) | 2023.11.21 |
| [Spring] @CreatedDate&@LastModifiedDate vs @CreationTimeStamp&@UpdateTimeStamp, 둘은 뭐가 다를까? (1) | 2023.11.21 |
| [Spring JPA] 페이징 Pageable, Page, Slice / 페이지 나누기 (2) | 2023.11.21 |