이슈
👉
(1) 일대다 관계에서 1+N 문제로 인해 불필요하게 N 번의 쿼리가 계속해서 발생했고, 페이징 처리를 동시에 해야 했다.
(2) List <Entity> 타입에 해당하는 요소들을 각각 query를 날려 여러 번의 select 문이 발생하는 비효율적인 상황이 있었다.
문제
👉
(1) N + 1 문제와 더불어 페이징 처리 구현 문제
내가 좋아요 한 게시글의 정보를 불러와야 해서, Page<Like> 에서 Board의 정보를 추출했다. getBoard()를 하는 만큼 N 번의 쿼리가 추가로 발생하여 비효율적이라는 생각이 들었다.
(내가 구현하는 API는 좋아요한 글의 정보를 조회하는 것이어서 좋아요 정보랑 게시글의 정보가 무조건 필요한 상황이었다.)
또한, Fetch Join을 사용해 CountQuery를 정상적으로 만들어 주지 못해 에러가 발생했다.
Spring Data JPA에서 제공하는 메서드 네이밍 쿼리 작성법을 제외하고, Fetch Join과 같이 페이징을 어떻게 구현해야 하는지 찾아보며 학습했다.
(2) List <Entity>에 따라 N번의 Select Query 발생
public List<Status> getLikeStatus(Member member, List<Board> boards) {
return boards.stream()
.map(board -> likeRepository.findByMemberAndBoard(member, board))
.map(Like::getStatus)
.toList();
}
위 코드는 로그인한 사용자가 여러 게시글 목록을 볼 때 좋아요 여부를 알려주기 위한 정보를 가져오는 로직이다.
Spring JPA를 통해 List <Board>의 요소마다 쿼리를 생성해 여러 번의 쿼리가 발생해 최적화되지 않은 코드라 생각이 들었다.
이를 한 번의 쿼리를 통해 할 수 없을까 라는 생각하고 전에 학습했던, IN 방법을 적용해 보았다.
해결
👉
(1) N + 1 문제와 더불어 페이징 처리 구현 문제 해결
Like와 Board는 일대다 관계로 N + 1 문제를 Fetch Join으로 해결할 수 있었다. JPQL을 통해 JOIN FETCH 문 작성을 통해 해결했다.
다음 문제로, Paging 처리를 위해 전체 Count를 처리하는 CountQuery가 필요했다.
JPA에서는 임의로 원본 쿼리를 보고 CountQuery를 생성하는데, Fetch Join을 사용하게 되어 제대로 생성되지 못해 CountQuery를 추가로 작성해 주어야 했다.
@Query(value = "SELECT c FROM Comment c JOIN FETCH c.board WHERE c.member = :member",
countQuery = "SELECT COUNT(c) FROM Comment c
INNER JOIN c.board WHERE c.member = :member")
Page<Comment> findByMemberFetchJoinBoard(@Param("member") Member member,
PageRequest pageRequest);
countQuery는 @Query 안에서 작성할 수 있다. COUNT(c)를 통해 발생한 결과의 카운트를 센다. countQuery에서는 Fetch Join이 아닌 INNER JOIN을 사용해야 한다. 페치 조인은 JPA에서만 제공하는 기능이므로, SQL 문법인 Inner Join을 통해 두 테이블의 관계를 설정해 주고 카운트를 해야 한다.
(2) List <Entity>에 따라 N번의 Select Query 해결
문제 2의 문제를 최적화하기 위해 JPQL과 IN 연산자를 통해 리스트에 해당하는 조건을 한 번에 쿼리를 날리는 방법을 적용해 보았다.
@Query("SELECT l.status FROM Like l WHERE l.member = :member AND l.board IN :boards")
List<Status> findStatusByMemberAndBoards(@Param("member") Member member, @Param("boards") List<Board> boards);
리스트의 자료형도 일반 파라미터처럼 @Param(”name”) 을 명시하면 적용할 수 있다.
IN을 통해 찾고 싶은 컬렉션을 한 번에 조회할 수 있다.
참고레퍼런스
JPA Fetch 조인(join)과 페이징(paging) 처리