영원히 남는 기록, 재밌게 쓰자

queryDSL을 사용해서 페이징 처리 해보기 본문

springboot/JPA

queryDSL을 사용해서 페이징 처리 해보기

youngjae-kim 2024. 4. 20. 14:22
728x90
반응형

피드백 조회를 할 때 한 학생에 대한 피드백을 띄워줄 때 모든 피드백을 다 가져와서 페이지에 뿌려주고 있다.

 

만약 데이터가 많이 쌓인다면 문제가 발생할 수 있고 하나의 피드백을 찾기 위해 스크롤을 많이 내려야 할 수 있다.

 

이를 queryDSL의 페이징을 사용하여 페이징 처리를 시도하려고 한다.

 

queryDSL에서는 페이징 처리를 할 때에 PageImpl을 사용한다.

 

PageImpl 사용 방식

 

설명을 조금 보았는데

 

content: 페이징을 적용한 결과가 담기는 부분

total: 전체 페이징 적용이 가능한 총 크기

 

content의 인자는 JPAQuery (queryDSL)의 fetch() 결과를 의미한다.

 

피드백을 조회하기 위한 fetch() 구문

List<RecordRespDto> fetch = query.select(
                new QRecordRespDto(record.id,
                        record.content,
                        record.teacher.name,
                        record.student.name,
                        record.status,
                        record.createDate,
                        record.modifiedDate)
        )
        .from(record)
        .where(record.status.eq(RecordStatus.PUBLISHED)
                .and(record.student.eq(student)))
        .offset(pageable.getOffset())
        .limit(pageable.getPageSize())
        .fetch();

 

페이징을 처리할 메서드의 Pageable 객체로 부터 pageable.getOffset() 값과 pageable.getPageSize() 값을

offset()과 limit()에 적용해줄 것이다.

 

offset()과 limit()

offset() 메소드는 반환될 결과의 시작점을 지정

limit() 메소드는 반환될 결과의 최대 개수를 지정

 

 

fetch() 와 fetchOne()

fetch()

목적: 여러 개의 결과를 조회할 때 사용.

반환 형태: 조회된 결과를 List 형태로 반환한다. 결과가 없는 경우 빈 리스트를 반환.

 

fetchOne()
목적: 단 하나의 결과만 조회할 때 사용. 주로 유니크한 조건으로 데이터를 조회할 때 사용.

반환 형태: 단일 객체를 반환한다. 결과가 없는 경우 null을 반환. 결과 값이 둘 이상이면 NonUniqueResultException 예외 발생.

 

new PageImpl()은 총 두 번의 쿼리를 실행하여 페이징을 적용한다는 것을 알 수 있었습니다.

 

@QueryProjection

이 어노테이션을 dto의 생성자에 적용하면 queryDSL에서 select 결과를 dto형태로 받을 수 있다.

select 절에 받아올 결과들 중 dto를 매핑하기 위한 데이터들만 뽑아서 넣어주면 된다.

 

 

 

PageableExecutionUtils를 사용하여 PageImpl() 방식의 count 쿼리 개선하는 방법

PageableExecutionUtils를 사용하면 count 쿼리를 날리는 방식 보다 비용이 저렴하다고 한다. 

다른 블로그를 참고 했을 때 첫 페이지와 마지막 페이지에서는 count 쿼리가 생성되지 않기 때문에 그런 것 같다.

(설명이 좋아서 공부할 겸 해당 블로그의 설명을 필사하였다.)

 

PageableExecutionUtils 역시 반환은 PageImpl 객체를 반환하는 것을 알 수 있었다.

 

PageImpl() 객체에서는 세번째 인자의 total 변수를 무조건 필수로 호출해야 하지만 PageableExecutionUtils 의 getPage에서는 totalSupplier라는 함수형 인터페이스를 통해 count 쿼리의 생성을 한번 막아주게 되어 비용이 조금 더 저렴하다고 하는 것 같다.

 


결과 테스트 해보기

@Test
public void createDummyRecordData(Student student, Teacher teacher) {
    for (int i = 0; i < 300; i++) {
        Record record = Record.builder()
                .student(student)
                .teacher(teacher)
                .content((i + 1) + "번 테스트 피드백 입니다.")
                .status(RecordStatus.PUBLISHED)
                .build();
        record.addStudent(student);
        record.addTeacher(teacher);

        recordRepository.save(record);
    }
}

@Test
void 페이징_처리_피드백_조회_테스트() {
    //given
    Student s = Student.builder()
            .name("철수")
            .userId("cs@time.com").password("1234")
            .phoneNumber("010-1111-2222").schoolName("용호초등학교")
            .classType(ClassType.ELEMENTARY).grade(1)
            .memberType(MemberType.STUDENT).gender(GenderType.MALE)
            .address(new Address("반림동", "현대 아파트", "102-1201"))
            .attendanceStatus(AttendanceStatus.Y)
            .build();
    Teacher t = new Teacher("줄리아", "1234", "010-1212-3456", MemberType.TEACHER, Position.TEACHER, "julia@time.com", GenderType.FEMALE);

    Student student = studentRepository.save(s);
    Teacher teacher = teacherRepository.save(t);

    createDummyRecordData(student, teacher);

    //when
    Pageable pageable = PageRequest.of(3, 10);
    Page<RecordRespDto> result = recordRepository.findAllPaging(student, pageable);

    //then
    log.info("result={}", result);
    assertThat(result.getSize()).isEqualTo(30); // 300개를 10개씩 페이징 처리해서 30개가 나와야 함

}

 

 

content = 10개, 요청 페이지 = 20 일 때 처음 페이지를 로딩하는 경우

 

페이징하고자 하는 사이즈가 더 커서 content의 size가 세번째 인자에 들어간다. 그리고 fetchOne()이 호출 되지 않으므로 count쿼리가 날아가지 않음

 

content = 10개, 요청 페이지 = 8 일 때 처음 페이지를 로딩하는 경우

 

 

이 경우는 세번째 인자로 count::fetchOne 이 호출 되면서 count 쿼리가 생성 되는 것을 확인할 수 있었다.

 

마지막 페이지 오프셋을 요청했을 때의 결과는 페이지 사이즈는 8개지만 가져온 결과는 2개를 가져오는 것을 확인할 수 있었다.

 

 

이 부분에서 알 수 있었던 것은 첫 페이지에 머무는 페이지가 많은 곳에는 이런 성능 최적화가 많이 이루어질 수 있다는 것을 알 수 있었다.

이 경우라면 내가 진행중인 서비스에 사용하면 좋겠다는 생각이 들었다.

 

 

페이징과 동작 원리를 잘 이해하지 못했는데 참고한 블로그를 통해 공부를 많이할 수 있었다.

 

728x90
반응형