-
N + 1 정복하기 <3. Fetch Join>프레임워크/Spring Boot 2024. 2. 21. 02:57
시리즈1. https://iwsaitw.tistory.com/entry/N-1-정복하기
시리즈2. https://iwsaitw.tistory.com/entry/N-1-정복하기-2-FetchTypeEAGER
(소스코드: https://github.com/blog-example/-JPA-N_Plus_1)
ORM을 사용하면 만나는 흔한 이슈 중 하나인 N + 1 !
명확하게 설명할 수 있을 정도로 머리속에 집어넣어보겠습니다.
이번 글에서는 fetch join을 활용해서 N + 1을 해결하는 과정을 살펴보겠습니다.
1. JPQL? Fetch Join?
Fetch Join 을 이야기 하기에 앞서서 JPQL을 먼저 이야기할 필요가 있습니다.
gpt가 뭐라고 정의하는지 한 번 보겠습니다.
JPQL은 개발자가 원하는 동작을 직접 데이터베이스에 전달할 수 있도록 JPA에서 제공하는 기능입니다. Fetch Join은 JPQL이 할 수 있는 기능 중에 연관된 엔티티나 컬렉션을 SQL의 JOIN을 사용해서 한 번의 쿼리로 조회하는 기능입니다.
즉 Fetch Join을 사용한다는 것은 JQPL의 기능 중 Fetch Join을 사용한다는 의미입니다.
String jpql = SELECT e FROM Employee e JOIN FETCH e.department WHERE e.salary > 50000
이전 글에서 살펴보았 듯이, N + 1은 필요한 데이터를 두 번의 쿼리로 나눠서 로드하면서 발생하는 이슈이고, 이를 JPQL을 사용해서 한 번의 쿼리로 로드하도록 만들어서 이슈를 해결할 것 입니다.
2. JPQL을 적용하는 방법들
1. Repository에서 @Query 어노테이션 사용
JPA가 default로 제공하던 findAll의 동작을 변경하는 방식입니다.
@Query 어노테이션을 추가해주고, 원하는 내용의 JPQL을 추가해줍니다.
public interface PostRepository extends JpaRepository<Post, Long> { @Query(value = "SELECT p FROM Post p JOIN FETCH p.comments") List<Post> findAll(); }
아주 간단하게 문제가 해결이 되었습니다.
이제는 데이터베이스에서 데이터를 fetch할 때 join을 사용해서 한번에 로드하는 것을 확인할 수 있습니다.
2. Service에서 Entity Manager 사용
이번에는 service 레이어에서 N + 1을 해결해보겠습니다.
위의 @Query 예제와 사용하는 모양만 조금 달라졌지 비슷한 느낌의 코드 진행입니다.
@PersistenceContext private final EntityManager entityManager; ...생략 public List<PostDto> getPosts() { String jqpl = "SELECT p FROM Post p JOIN FETCH comments"; List<Post> posts = entityManager.createQuery(jqpl, Post.class).getResultList(); // 기존 findAll을 대체 List<PostDto> postDtos = new ArrayList<>(); for (int i = 0; i < posts.size(); i++) { Post post = posts.get(i); List<Comment> comments = post.getComments(); List<CommentDto> commentDtos = new ArrayList<>(); for (Comment comment: comments) { commentDtos.add(CommentDto.of(comment, post.getPostId())); } postDtos.add(PostDto.of(post, commentDtos)); } return postDtos; }
마찬가지로 join을 통해서 한번에 데이터를 로딩하고 있습니다.
JPQL을 사용하니 상당히 간단하게 N + 1 이슈가 해결되었습니다.
기본적인 접근은 N + 1은 JPA가 연관관계가 있는 데이터를 로딩할 때 최적화를 위해 2단계의 쿼리로 나눠서 데이터를 가져오기 때문에 발생하는 것이고, 이것을 해결하기 위해서 1번의 쿼리로 데이터를 로딩하도록 강제하는 방향으로 진행됩니다.
이 글에서 다룬 내용은 페이징 처리를 하지 않기 때문에 간단하게 해결이 됩니다.
하지만 페이징이 추가 된다면 복잡도가 올라가기 때문에, 페이징 처리가 되는 케이스는 뒤에서 다시 다루어보겠습니다.
감사합니다.
'프레임워크 > Spring Boot' 카테고리의 다른 글
N + 1 정복하기 <5. Entity Graph> (0) 2024.02.21 N + 1 정복하기 <4. @BatchSize> (0) 2024.02.21 N + 1 정복하기 <2. FetchType.EAGER> (0) 2024.02.20 N + 1 정복하기 <1. N + 1?> (1) 2024.02.20 데이터베이스를 기반으로 JPA의 연관관계 살펴보기 <1> (0) 2024.02.06