상세 컨텐츠

본문 제목

[SPRING] Querydsl | 스프링 데이터 JPA로 페이징하기

JAVA/기본 & 강의복습

by ranlan 2021. 5. 18. 02:59

본문

728x90

스프링 데이터 JPA 페이징

Spring Data JPA는 페이징 처리를 위해 Pageable, Page 인터페이스와 Page 구현체인 PageImpl를 제공한다.

 

Pageable

/**
 * Abstract interface for pagination information.
 */
public interface Pageable { ... }

Page

/**
 * A page is a sublist of a list of objects. It allows gain information about the position of it in the containing
 * entire list.
 *
 * @param <T>
 */
public interface Page<T> extends Slice<T> { ... }

PageImpl

/**
 * Basic {@code Page} implementation.
 *
 * @param <T> the type of which the page consists.
 */
public class PageImpl<T> extends Chunk<T> implements Page<T> { 

    private static final long serialVersionUID = 867755909294344406L;

	private final long total;

	/**
	 * Constructor of {@code PageImpl}.
	 *
	 * @param content the content of this page, must not be {@literal null}.
	 * @param pageable the paging information, must not be {@literal null}.
	 * @param total the total amount of items available. The total might be adapted considering the length of the content
	 *          given, if it is going to be the content of the last page. This is in place to mitigate inconsistencies.
	 */
	public PageImpl(List<T> content, Pageable pageable, long total) {

		super(content, pageable);

		this.total = pageable.toOptional().filter(it -> !content.isEmpty())//
				.filter(it -> it.getOffset() + it.getPageSize() > total)//
				.map(it -> it.getOffset() + content.size())//
				.orElse(total);
	}

	/**
	 * Creates a new {@link PageImpl} with the given content. This will result in the created {@link Page} being identical
	 * to the entire {@link List}.
	 *
	 * @param content must not be {@literal null}.
	 */
	public PageImpl(List<T> content) {
		this(content, Pageable.unpaged(), null == content ? 0 : content.size());
	}

 

사용자 정의 레퍼지토리에 페이징 메서드 추가

MemberRepositoryCustom

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import study.querydsl.dto.MemberSearchCondition;
import study.querydsl.dto.MemberTeamDto;

public interface MemberRepositoryCustom {

    Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable);
    Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable);

}

반환값은 Page<MemberTeamDto>이며 Pageable 전달

 

사용자 정의 레퍼지토리에 정의한 메서드 구현

조회 쿼리와 total count 쿼리 한번에 실행 (발생되는 쿼리는 2번)

@Override
public Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable) {
    QueryResults<MemberTeamDto> result = queryFactory
            .select(new QMemberTeamDto(
                    member.id.as("memberId"),
                    member.username,
                    member.age,
                    team.id.as("teamId"),
                    team.name.as("teamName")
            ))
            .from(member)
            .leftJoin(member.team, team)
            .where(usernameEq(condition.getUsername()),
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe()))
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetchResults();

    List<MemberTeamDto> content = result.getResults();
    long total = result.getTotal();

    return new PageImpl<>(content, pageable, total);
}

count 쿼리 시 필요없는 order by는 알아서 제외

 

조회 쿼리와 total count 쿼리 분리

데이터 조회용 쿼리와 전체 사이즈를 조회하는 쿼리 따로 관리

import org.springframework.data.support.PageableExecutionUtils;

@Override
public Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable) {

    //content
    List<MemberTeamDto> content = queryFactory
            .select(new QMemberTeamDto(
                    member.id.as("memberId"),
                    member.username,
                    member.age,
                    team.id.as("teamId"),
                    team.name.as("teamName")
            ))
            .from(member)
            .leftJoin(member.team, team)
            .where(usernameEq(condition.getUsername()),
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe()))
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetch();

    // count 
    JPAQuery<Member> countQeury = queryFactory
            .select(member)
            .from(member)
            .leftJoin(member.team, team)
            .where(usernameEq(condition.getUsername()),
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe()));

    // return PageableExecutionUtils.getPage(content, pageable, () -> countQuery.fetchCount());
    return PageableExecutionUtils.getPage(content, pageable, countQeury::fetchCount);
}

 

PageableExecutionUtils 이용하여 필요 시에만 count 쿼리 실행 가능

- 스프링 데이터 라이브러리가 제공하며 count 쿼리가 생략 가능한 경우 생략해서 처리한다.

  예) 페이지 시작이면서 컨텐츠 사이즈가 페이지 사이즈보다 작을 때 전체 사이즈가 필요 없음, 마지막 페이지 일 때 offset + 컨텐츠 사이즈를 더하면 전체 사이즈 조회 가능

 

컨트롤러

MemberController

import org.springframework.data.domain.Pageable;

@GetMapping("/paging/members")
public Page<MemberTeamDto> searchMember(MemberSearchCondition condition, Pageable pageable) {
    return memberRepository.searchPageComplex(condition, pageable);
}

동일하게 반환값은 Page<MemberTeamDto>

 

실행 결과 조회 결과 size 개수대로 데이터와 페이징 현황을 보여줌

http://localhost:8080/paging/members?page=0&size=5

 

 

 

 

728x90

관련글 더보기

댓글 영역