Post

(Project)페이징 처리 시 발생한 문제

페이징 처리 시 발생한 문제

문제 상항

  • 현재 Issue및 Page 상태
1
2
3
4
-   Issue Entity는 Member와 Milestone Entity에 대해 @ManyToOne 관계를 맺고 있다.
-	Milestone 의 경우 Issue에 등록 되어 있을 수도 있고 없을 수도 있다.
-   Issue 를 OPEN, COLSE 상태 별로 전체 조회하는 로직이 필요하다.
-   첫 시작 Page 는 1로 해야하고, Size는 25이다.
  • Issue-tracker 프로젝트를 진행하면서 Issue 조회에 대해 페이징 처리를 하던 중 문제가 두 가지 발생하였다.

  • 첫번째 문제로는 Issue에 Milestone 이 없는 경우 전체 조회를 진행하여도 리스트에 나타나지 않는 문제가 있었다.
  • 두번째 문제로는 query specified join fetching, but the owner of the fetched association was not present in the select list 라는 문구의 예외가 발생하였다.

문제 코드

1
2
@Query(value = "select i from Issue i join fetch i.member m join fetch i.milestone mi where i.status = :status")
Page<Issue> findIssues(Pageable pageable, @Param("status") IssueStatus status);
  • 위 코드가 OPEN, CLOSE 상태 별 Issue 를 전체 조회 하는 코드이다.
  • 딱 봤을때 문제가 없는 코드라고 생각하였는데, 두 가지 문제가 발생하여 많이 당황했다.

첫번째 문제

  • 상태별 Issue를 조회 할 때 해당 Issue에 Milestone 이 등록되어 있지 않은 경우 Issue를 조회 해도 리스트에 나타나지 않는 문제가 있다.

문제 원인

  • 그냥 join fetch 를 했을때 어떤 join 이 발생하는지 알고 있다면 쉽게 해결할 수 있는 문제다. 그냥 join fetch 를 하면 inner join이 발생하게 되는데 inner join의 경우 join 대상이 null 일 경우 데이터를 가져오지 않기 때문에 Issue를 조회할때 Milestone이 없을 경우 리스트에 나타나지 않는것이였다.

해결 방법

  • left join fetch 를 하게되면 left join 이 발생하기 때문에 join 대상인 Milestone 에 값이 없어도 Issue를 가져오기 때문에 이 방법으로 해결 할 수 있었다.

해결 코드

1
2
@Query(value = "select i from Issue i left join fetch i.member m left join fetch i.milestone mi where i.status = :status")
Page<Issue> findIssues(Pageable pageable, @Param("status") IssueStatus status);

두번째 문제

  • 이번에는 query specified join fetching, but the owner of the fetched association was not present in the select list 라는 문구의 예외가 발생하면서 실행조차 되지 않았다.
  • 우선 N+1 문제를 해결하기 위해 @ManyToOne 관계를 가진 Entity를 join fetch 시켜주었다. 그리고 Issue의 상태별 조회를 위해 where 문을 사용하였다.
  • @ManyToOne 관계에 join fetch 를 하는것은 데이터의 뻥튀기가 없기 때문에 이부분은 문제가 아니라 생각했다.
  • join fetch 시 where문을 사용하는것은 join 대상에 where 문을 사용하는 것이 아니라면 문제가 없기 때문에 이부분도 문제가 아닐것이라 생각했다.

해결 방법

  • 해결 방법이 도저히 생각이 안나 검색을 하기 시작했다. 다행히 stackoverflow 나와 같은 문제를 겪고 있는 사람의 글을 보게되었다.
  • 해결 방법으로는 count query를 따로 작성해 주면 된다는 것이였다. 이부분에서 오잉? 하고 의문이 들었다. 알고 있기로는 페이징 처리를 하면 임의로 count query를 생성해서 날려주는 것으로 알고 있었기 때문이다.
  • 검색을 통해 문제를 해결하긴 했지만, 여전히 왜 이런 문제가 발생하는지 너무 궁금하여 인프런을 통해 영한님께 질문을 드렸다.

문제 원인

  • 인프런에 달려있던 영한님의 답변으로 왜 이런 문제가 발생하는지 알 수 있었다.
  • JPA가 임의로 날려주는 쿼리의 경우 JPA가 많이 똑똑하지 못해 조회 결과가 바뀌어 버리기 때문에 정상적인 count의 값을 가져올 수 없어 발생 하는 문제라고 하셨다.

  • fetch join 이나 where 문을 함께 사용하는 복잡한 쿼리의 경우 count query를 꼭 분리해서 사용해야한다는 답을 얻었다.

해결 코드

1
2
3
@Query(value = "select i from Issue i left join fetch i.member m left join fetch i.milestone mi where i.status = :status",
       countQuery = "select count(i) from Issue i left join i.member left join i.milestone where i.status = :status")
Page<Issue> findIssues(Pageable pageable, @Param("status") IssueStatus status);

REFERENCE

This post is licensed under CC BY 4.0 by the author.