DEV/Backend

오프셋 기반 페이지네이션

Beomsu Koh 2023. 6. 9.

오프셋 기반 페이지네이션

오프셋 기반의 페이지네이션은 페이지를 조회하기 위해 오프셋(offset)과 크기(size)를 사용하는 방식입니다.
이 방식은 특정 페이지의 데이터를 요청할 때, 이전 페이지까지의 데이터를 스캔해야 하는 단점이 있습니다.

이에 따라 성능 이슈가 발생할 수 있습니다.

오프셋 기반의 페이지네이션 구현

레포지터리 구성

먼저 오프셋 기반의 페이지 구성을 사용하도록 레포지터리를 구성해야 합니다.
레포지터리 인터페이스에서 findAllBy 메서드를 정의합니다.

public Page<Post> findAllByMemberId(Long memberId, Pageable pageRequest) {  
	var params = new MapSqlParameterSource()  
		.addValue("memberId", memberId)  
		.addValue("offset", pageRequest.getOffset())  
		.addValue("size", pageRequest.getPageSize());  
		  
	Sort sort = pageRequest.getSort();  
	String query = String.format("""  
			SELECT *  
			FROM %s  
			WHERE memberId = :memberId  
			ORDER BY %s  
			LIMIT :offset, :size  
			""", TABLE, PageHelper.orderBy(sort));  
		  
	var posts = namedParameterJdbcTemplate.query(query, params, ROW_MAPPER);  
	return new PageImpl<Post>(posts, pageRequest, getCount(memberId));  
}

이 코드에서 findAllByMemberId 메소드는 memberIdpageable 매개변수를 허용하여 오프셋 기반 페이지 구성을 기반으로 특정 회원의 게시물을 검색합니다.

서비스 구성

다음으로 오프셋 기반 페이지 구성을 활용하도록 서비스 계층을 구성합니다. 방법은 다음과 같습니다.

  • 리포지토리를 서비스 클래스에 삽입합니다.
@Service
public class PostService {
    private final PostRepository postRepository;

}
  • 서비스 클래스에서 오프셋 기반 페이지 매김을 사용하여 게시물을 검색하는 방법을 구현합니다.
private PostDto toDto(Post post) {  
	return new PostDto(  
	post.getId(),  
	post.getMemberId(),  
	post.getContents(),  
	post.getCreatedAt(),  
	postLikeRepository.countByPostId(post.getId())  
	);  
}

public Page<PostDto> getPostDtos(Long memberId, Pageable pageRequest) {  
	return postRepository.findAllByMemberId(memberId, pageRequest).map(this::toDto);  
}

위의 코드에서는 원하는 페이지와 크기로 PageRequest 개체를 생성한 다음 이를 사용하여 저장소의 findAllByMemberId 메서드를 호출합니다.

컨트롤러 구성

마지막으로 오프셋 기반 페이지 매김 요청을 처리하도록 컨트롤러를 구성합니다. 방법은 다음과 같습니다.

  • 컨트롤러 클래스를 정의하고 여기에 서비스를 주입합니다.
@RestController
@RequestMapping("/members")
public class PostController {
    private final PostService postService;
}
  • 컨트롤러에서 오프셋 기반 페이지 매김을 사용하여, 게시물을 검색하는 엔드포인트를 구현합니다.
@GetMapping("/members/{memberId}")  
public Page<PostDto> getPosts(  
	@PathVariable Long memberId,  
	Pageable pageRequest  
	) {  
	return postReadService.getPostDtos(memberId, pageRequest);  
}

이 코드에서는 /members/{memberId}/posts 엔드포인트에 대한 GET 매핑을 정의하고 @RequestParam 주석을 사용하여 오프셋 기반 페이지 매김에 대한 pagesize 매개변수를 지정합니다.

페이지 정렬

페이지네이션 기능에 추가로 정렬 기능을 구현 할 수 있습니다.
아래는 페이지 정렬을 위해 PageHelper 클래스를 사용하는 예제 코드입니다

public class PageHelper {
    public static String orderBy(Sort sort) {
        if (sort.isEmpty()) {
            return "id DESC";
        }

        List<Sort.Order> orders = sort.toList();
        List<String> orderByList = orders.stream()
                .map(order -> order.getProperty() + " " + order.getDirection())
                .toList();

        return String.join(", ", orderByList);
    }
}

PageHelper 클래스는 Sort 객체를 받아서 정렬 정보를 문자열로 변환해주는 유틸리티 클래스입니다.
이를 활용하여 페이지네이션 쿼리에 정렬 정보를 추가할 수 있습니다.

예를 들어, Pageable 객체에 정렬 정보가 포함되어 있다면, PageHelper.orderBy 메서드를 사용하여 정렬 정보를 문자열로 변환한 후 페이지네이션 쿼리에 추가합니다.

컨트롤러에서는 아래와 같이 페이지네이션 및 정렬 기능을 사용할 수 있습니다.

@GetMapping("/members/{memberId}")
public Page<PostDto> getPosts(
        @PathVariable Long memberId,
        @RequestParam Integer page,
        @RequestParam Integer size,
        @RequestParam(required = false, defaultValue = "id,desc") String sort
) {
    Sort pageableSort = Sort.by(Sort.Direction.DESC, sort);
    Pageable pageRequest = PageRequest.of(page, size, pageableSort);
    return postReadService.getPostDtos(memberId, pageRequest);
}

위 코드에서는 /members/{memberId} 엔드포인트를 통해 특정 회원의 게시물 목록을 페이지네이션 및 정렬 기능을 통해 조회할 수 있습니다.
pagesize는 페이지 정보를, sort는 정렬 정보를 받아와서 PageRequest 객체를 생성합니다.

이제 오프셋 기반의 페이지네이션을 구현하고 페이지 정렬을 추가할 수 있게 되었습니다.

부족한 점이나 잘못 된 점을 알려주시면 시정하겠습니다 :>

'DEV > Backend' 카테고리의 다른 글

커버링 인덱스  (0) 2023.06.09
커서 기반 페이지네이션  (0) 2023.06.09
페이지네이션  (0) 2023.06.09
조회 최적화를 위한 인덱스  (2) 2023.06.02
DTO(Data Transfer Object)  (0) 2023.06.02

댓글