다음과 같은 테이블 구조에서 쿼리를 작성하게 되었다.
필요한 쿼리는 memberId로 pomodoro_prgoress들을 찾아서 pomodoro_room을 조회하는 것.
1. 쿼리를 두 번 나눠서 실행하기
List<PomodoroProgress> pomodoroProgresses = em.createQuery("select pp from PomodoroProgress pp where pp.member.id = :memberId", PomodoroProgress.class)
.setParameter("memberId", memberId)
.getResultList();
먼저 memberId가 같은 pomodoroProgress를 찾는다.
List<PomodoroRoom> pomodoroRooms = pomodoroProgresses.stream()
.map(PomodoroProgress::getPomodoroRoom)
.distinct()
.toList();
그 다음 메모리에서 스트림을 돌면서 getPomodoroRoom을 통해 객체 그래프 탐색으로 PomodoroRoom들을 찾는다. PomodoroProgress와 PomodoroRoom은 다대일 관계이기 때문에 distinct를 해주었다. 프록시 객체를 생성할 때 id가 같으면 동일한 객체를 반환하는 것 같다. distinct를 제거하고 중복을 만들어서 출력을 해보니 해시 코드와, 동일한 프록시 객체임을 확인했다.
주의해야 할 점은 getPomodoroRoom까지만 수행했을 때는 PomodoroRoom이 프록시 상태이지만 distinct 연산을 수행하게 되면 쿼리를 수행해 실제 객체를 가져온다는 점을 인지해야 한다.
이러한 방식은 메모리에서 스트림을 돌면서 PomodoroRoom을 찾기 때문에 데이터가 많아지면 메모리 문제나 속도 문제가 발생할 수 있다.
2. 서브 쿼리
return em.createQuery("select pr from PomodoroRoom pr where pr in (select pp.pomodoroRoom from PomodoroProgress pp where pp.member.id = :memberId)", PomodoroRoom.class)
.setParameter("memberId", memberId)
.getResultList();
1번에서 수행한 쿼리보다 조금 더 나은 선택지. 한 번에 쿼리를 수행하며 메모리에서 연산을 하지도 않는다.
작성하기 쉽고 직관적이기도 하다.
하지만 서브 쿼리는 성능 최적화가 잘 안되고 느리다는 단점을 갖고 있다.
직접 확인해보기 위해 memberId가 1이고 pomodoroRoom1을 참조하는 PomodoroProgress를 1만개 저장하고 조회해봤더니 위와 같이 크게 느리지는 않은 것을 확인했다.
3. 조인
가장 빠른 방법.
return em.createQuery("select pr from PomodoroRoom pr join pr.pomodoroProgresses pp where pp.member.id = :memberId", PomodoroRoom.class)
.setParameter("memberId", memberId)
.getResultList();
쿼리가 한 번에 수행된다.
그런데 속도를 측정해보니 조인 쿼리가 더 느리다...?
서브쿼리를 사용하는 jpql쿼리를 수행했을 때 발생하는 sql쿼리를 복사해서 쿼리 실행계획을 확인해봤다.
풀테이블 스캔을 실행하며 인덱스를 타지 않는다. 그런데 select_type을 보면 jpql 쿼리를 서브 쿼리로 작성했음에도 subquery가 아닌 simple로 조회가 되는것을 확인했다. 다시 복사해온 쿼리를 확인해 보니 sql쿼리는 서브 쿼리로 수행되지 않고 있다.
조인쿼리의 쿼리 실행계획을 확인해봤다.
pomodoro_room인 p1은 풀테이블 스캔을 실행하며 인덱스를 타지 않고, pomodoro_progress인 p2는 기본키로 인덱스를 탄다.
두 쿼리 모두 simple 타입으로 수행되고 있고 인덱스 등 최적화도 똑같이 이루어지고 있지 않기 때문에 하나의 테이블을 조회하는 서브 쿼리 쪽이 더 속도가 빨랐다는 결론을 내릴 수 있다.
결론
원하는 데이터를 조회하기 위한 여러 방법이 쿼리를 작성해 보았고 장단점들을 비교해보았다.
단순히 궁금증에서 시작했던 시간 측정에서 jpql을 서브 쿼리가 더 빨리 수행된다는 예상과는 다른 결과를 확인했고, 이 원인을 분석하기 위해 실제 수행되는 sql 문으로 쿼리 실행 계획을 확인해보았다. 서브쿼리로 작성한 jpql의 실제로 수행되는 sql문은 서브쿼리가 아니였고 이에 따라 더 빠른 속도를 내는 것을 확인했다. 쿼리의 속도를 예상만 하지 말고 속도를 측정하고 쿼리 실행 계획을 확인하자.
'회고 > 우아한테크코스' 카테고리의 다른 글
[JPA] 테스트 데이터를 엔티티로 셋팅하며 발생한 이슈 (0) | 2023.09.27 |
---|---|
@PathVariable(required = false)를 하더라도 /{pathVariable} 경로가 일치해야 핸들링 된다 (0) | 2023.08.17 |
2023.08.08 일일 회고 (0) | 2023.08.09 |
2023.07.31 일일 회고 (1) | 2023.08.01 |
2023.07.21 일일 회고 (0) | 2023.07.21 |