N+1문제에 대해 공부하다보니 FetchJoin과 EntityGraph가 해결방안이라는 것을 알게 됐고 연장선상에서 두 가지에 대해 더 공부했다. 가지가 많아서 이것저것 다 공부하다보니 막상 해결책은 공부를 제일 마지막에 하게 되었다.
서론이 길었다. Fetch Join에 대해 알아보자.
FetchJoin이란?
FetchJoin은 JPQL에서 성능 최적화를 위해 사용하는 기능이다.
FetchJoin은 SQL의 조인 종류가 아니다.
(SQL 조인에는 Inner Join, Outer Join(left,right, full)이 있다.)
JPA는 엔티티에 관계를 맵핑할 때 지연 로딩과 즉시 로딩을 설정할 수 있는데 이때 성능 최적화를 진행한다.
즉시 로딩 -> 어떠한 엔티티가 조회되었을 때 연관된 엔티티도 전부 조회하는 것
지연 로딩 -> 어떠한 엔티티가 조회되었을 때 연관된 엔티티는 Proxy로 들어가게 되고, 실제 사용될 때 DB를 조회해 사용
EAGER 옵션은 연관된 엔티티를 추가로 SQL을 조회할 필요없이 모두 가져오기에 매력적이나 문제가 있다.
이것이 바로 직전에 포스팅했던 N+1문제에 관한 것이다.
1. 엔티티를 조회하지만, 연관된 엔티티는 필요하지 않아도 조회하기에 성능에 문제가 있다.
2. 사용하지 않는 관련된 엔티티를 찾아오며 개발자의 의도와는 별개로 Query를 발생시키기 때문에 N+1문제가 발생하는 것이다.
여튼 N+1이 발생하는 문제에 있어서 더 궁금하다면 이전에 포스팅했던 글을 참고하자.
물론 LAZY 옵션을 쓴다해서 Query가 추가 발생되지 않거나, N+1문제에 대해 자유로운 것은 아니다.
아무튼 Query가 추가로 발생하는 경우에 Fetch Join을 사용하면 된다.
엔티티 조회 시, Fetch Join을 쓰면 개발자의 의도에 따라 한 번의 Query로 연관된 엔티티를 같이 조회할 수 있다.
연관된 엔티티가 필요하지 않을 때는 Proxy가 들어오며, 필요할 때는 실제 객체를 조회할 수 있게 되어 로딩의 문제점을 보완하게 된다.
(단 N:1 관계에서 1에서 Fetch Join을 사용시 페이징이 되지 않으니 주의해야한다!)
✔일반 Join과 Fetch Join의 차이는 무엇일까
[일반 Join]
- Fetch Join과는 달리 연관 Entity에 Join을 걸어도 실제 쿼리에서 SELECT하는 Entity는 오직 JPQL에서 조회하는 주체가 되는 Entity만 조회해 영속화시킨다
- 조회의 주제가 되는 Entity만 SELECT 해서 영속화하기 때문에 데이터는 필요하지 않지만 연관 Entity가 검색 조건에 필요한 경우 주로 사용한다
[Fetch Join]
- 조회의 주체인 Entity 외에 Fetch Join이 걸린 연관 Entity도 함께 SELECT 한 뒤에 전부 영속화시킨다.
- Fetch Join이 걸린 Entity 모두 영속화시키기에 FetchType이 LAZY인 Entity를 참조하더라도 이미 영속성 컨텍스트 안에 들어있기 때문에 따로 쿼리가 실행되지 않아 N+1문제가 해결된다.
영속성에 대해 궁금한 이들을 위해 네,맞아요.제가 궁금합니다
영속성이란? '사라지지 않고 지속되게 한다'가 사전적 의미지만, DB에 저장된다는 의미이다.
Fetch Join?! 그래서 언제쓸꼬하니
N+1문제와 성능 최적화를 할 수 있고, 지연로딩을 쓸 때면 FetchJoin을 써라 이말이여
FetchJoin의 한계
1. Fetch Join 대상에는 별칭을 줄 수 없다
JPA 표준에서는 지원하지 않으나 HIBERNATE를 포함한 몇몇의 구현체들은 FetchJoin에 별칭을 지원하나, 별칭을 잘못 사용하게 되면 연관된 데이터의 수가 달라져 데이터 무결성이 깨질 가능성이 있어 사용하지 않는 것이 좋다.
2. 둘 이상의 컬렉션을 패치할 수 없다
구현체에 따라 컬렉션*컬렉션의 카테시안 곱이 만들어져 주의해야한다. 하이버네이트를 사용 시 예외가 발생한다.
3. 컬렉션을 FetchJoin하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없다
- 컬렉션(1:N)이 아닌 단일 값 연관 필드(1:N, N:1)들은 FetchJoin을 사용해도 페이징 API를 사용할 수 있다.
- HIBERNATE에서 컬렉션을 FetchJoin하고 페이징 API를 사용하면 경고 로그를 보낸다.
- 데이터가 적으면 상관없으나 데이터가 많다면 성능 문제와 메모리 초과 예외가 발생 가능하다.
- 컬렉션을 FetchJoin시에 페이징API를 사용하게 되면 메모리에서 페이징 처리를 진행한다.
카테시안 곱(Cartesian Product)란?
union(교집합)/interaction(합집합)
-> From 절에 2개 이상의 Table이 있을 때 두 Table 사이에 유효 join 조건을 적지 않았을때 해당 테이블의 모든 데이터를 전부 결합해 존재하는 행 갯수를 곱한 만큼의 결과값이 반환되는 것이다.
즉, join 쿼리 중에 WHERE 절에 기술하는 join 조건이 잘못 기술되었거나 아예 없을 경우 발생하는 것이다.
참고 자료:
자바 ORM 표준 JPA 프로그래밍(김영한)
https://dingdingmin-back-end-developer.tistory.com/entry/SpringBoot-Data-Jpa-5-Fetch-Join
https://frogand.tistory.com/156
https://devlogofchris.tistory.com/29
'책벌레와 벌레 그 사이 어딘가 > 개념쌓기' 카테고리의 다른 글
[개념쌓기] HATEOAS? (0) | 2023.01.04 |
---|---|
[개념쌓기] EntityGraph (0) | 2023.01.04 |
[개념쌓기] N+1 문제 (0) | 2023.01.02 |
[개념쌓기] EAGER & LAZY (0) | 2023.01.02 |
[개념쌓기] 오버로딩&오버라이딩 (0) | 2022.12.31 |
댓글