Spring에서 Page 사용법

Tak Jeon·2025년 1월 31일
0

Spring

목록 보기
4/8
post-thumbnail

Page?

Page<T>란 Spring Data에서 제공하는 페이징 처리를 위한 인터페이스로서, List<T>와 비슷하지만 총 데이터 개수, 페이지 번호, 전체 페이지 수 등의 정보를 추가적으로 제공하는 것이 특징.

Page의 주요 기능

  • getContent() : 현재 페이지의 데이터 리스트 반환 (List)
  • getTotalElements() : 전체 데이터 개수
  • getTotalPages() : 전체 페이지 개수 (총 데이터 개수 / 페이지 크기)
  • getSize() : 한 페이지 당 데이터 개수
  • getNumber() : 현재 페이지 번호
  • hasNext() : 다음 페이지가 존재하는지
  • hasPrevious() : 이전 페이지가 존재하는지
  • isFirst() : 첫 번째 페이지인지
  • isLast() : 마지막 페이지인지

Page를 사용할 경우, Pageable 객체를 사용하여 크기, 페이지 번호 등을 입력한 뒤 Page에 적용하여 사용하는 방법이 일반적이다.

//적용 예시
Pageable pageable = PageRequest.of(0, 10, Sort.by("createdAt").descending());
Page<Todo> todoPage = todoRepository.findAll(pageable);
  • Pageable을 Repository에 전달하면 자동으로 페이징 처리 된 결과(Page<T>)가 반환된다.

Page 사용 예제

1. Page<T>를 사용하여 데이터 조회하기

@RestController
@RequestMapping("/todos")
public class TodoController {
    private final TodoService todoService;

    public TodoController(TodoService todoService) {
        this.todoService = todoService;
    }

    @GetMapping
    public ResponseEntity<Page<Todo>> getTodos(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        
        Pageable pageable = PageRequest.of(page, size);
        Page<Todo> todos = todoService.getTodos(pageable);
        return ResponseEntity.ok(todos);
    }
}

위와 같이 page, size 값을 쿼리 파라미터로 받아오고, 받아온 데이터를 사용해 Pageable 객체를 생성한 뒤, Service단으로 넘겨주어 처리해준다.

2. Service에서 Page<T> 반환하기

@Service
public class TodoService {
    private final TodoRepository todoRepository;

    public TodoService(TodoRepository todoRepository) {
        this.todoRepository = todoRepository;
    }

    public Page<Todo> getTodos(Pageable pageable) {
        return todoRepository.findAll(pageable);
    }
}

위와 같이 Controller에서 Service 메서드로 pageable 객체와 같이 넘어올 경우, 해당 pageable 객체를 사용해 repository단으로 넘겨주어 데이터를 처리한 뒤, 반환해준다.

3. Repository에서 Page<T> 처리

//JDBC를 사용할 경우
@Repository
public class TodoRepositoryImpl implements TodoRepository {
	private final JdbcTemplate jdbcTemplate;

    public TodoRepositoryImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    
    @Override
    public Page<TodoResponseDto> findTodos(Pageable pageable) {
        int pageSize = pageable.getPageSize();
        int pageNumber = pageable.getPageNumber();
        int offset = pageNumber * pageSize;

        List<TodoResponseDto> todos = jdbcTemplate.query("select * from todo t join user u on t.user_id = u.id limit ? offset ?",
                todoResponseDtoRowMapper(), pageSize, offset);
        int total = getTotalCount();

        return new PageImpl<>(todos, pageable, total);
    }
    
    private RowMapper<TodoResponseDto> todoResponseDtoRowMapper() {
        return new RowMapper<TodoResponseDto>() {
            @Override
            public TodoResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
                return new TodoResponseDto(
                        rs.getLong("id"),
                        rs.getString("contents"),
                        rs.getLong("user_id"),
                        rs.getString("name"),
                        rs.getString("password"),
                        rs.getTimestamp("create_date").toLocalDateTime(),
                        rs.getTimestamp("edit_date").toLocalDateTime()
                );
            }
        };
    }
}

//JPA를 사용할 경우
@Repository
public interface TodoRepository extends JpaRepository<Todo, Long> {
    Page<Todo> findAll(Pageable pageable);
}

JDBC로 구현할 경우, pageSize, offset을 설정한 뒤, jdbcTemplate.query()를 사용하여 조건에 맞는 데이터를 가져오고, 해당 데이터를 PageImpl<>에 담아 반환한다.

JPA로 구현할 경우 코드가 매우 깔끔해진다! (JPA 짱짱!)

4. Page<T>의 JSON 응답 예시

위 과정을 거치면, 아래와 같은 형태의 json 데이터가 응답하게 된다.

{
  "content": [
    {
      "id": 1,
      "contents": "할 일 1",
      "userId": 3,
      "password": "1234",
      "createDate": "2024-01-31T12:00:00",
      "editDate": "2024-01-31T13:00:00"
    },
    {
      "id": 2,
      "contents": "할 일 2",
      "userId": 3,
      "password": "5678",
      "createDate": "2024-01-31T14:00:00",
      "editDate": "2024-01-31T15:00:00"
    }
  ],
  "pageable": {
    "pageNumber": 0,
    "pageSize": 2
  },
  "totalElements": 10,
  "totalPages": 5
}

Pageable

Pageable은 Spring Data에서 페이징 요청을 처리하는 객체로서 페이징을 수행할 때 페이지 번호(page), 페이지 크기(size), 정렬(sort)등의 정보를 포함함.

Pageable의 주요 기능

  • getPageNumber() : 현재 페이지의 번호
  • getPageSize() : 한 페이지당 데이터 개수
  • getSort() : 정렬 정보
  • getOffset() : 현재 페이지에서 시작하는 데이터의 offset 값(page * size)
  • isPaged() : 페이징이 적용되었는지

Pageable 객체 생성 방법

1. PageRequest.of(page,size,sort)를 사용

//적용 예시
Pageable pageable = PageRequest.of(0, 10, Sort.by("createdAt").descending());

2. @RequestParam을 이용한 Pageable 자동 주입
Spring Boot에서는 Controller에서 Pageable을 직접 받는 것이 가능

@RestController
@RequestMapping("/todos")
public class TodoController {
    private final TodoService todoService;

    public TodoController(TodoService todoService) {
        this.todoService = todoService;
    }

    @GetMapping
    public ResponseEntity<Page<Todo>> getTodos(Pageable pageable) {
        Page<Todo> todos = todoService.getTodos(pageable);
        return ResponseEntity.ok(todos);
    }
}

위와 같이 구현하면, Client가 URL에서 page, size, sort를 쿼리 파라미터로 넘길 수 있다.

//요청 예시
GET /todos?page=1&size=5&sort=createdAt,desc

위의 요청이 들어오면 자동으로 아래와 같이 Pageable로 변환되게 된다

PageRequest.of(1, 5, Sort.by("createdAt").descending());

PageImpl<>

PageImpl<T>은 Spring Data의 Page<T> 인터페이스를 구현한 클래스로, 커스텀 페이징 결과를 직접 생성할 때 사용한다.
보통 Spring Data JPA를 사용하지 않고, JDBC, QueryDSL, Native Query등을 사용할 때 우용함.

PageImpl<T>의 주요 기능

  • getContent() : 현재 페이지의 데이터 리스트 반환 (List)
  • getTotalElements() : 전체 데이터 개수
  • getTotalPages() : 전체 페이지 개수 (총 데이터 개수 / 페이지 크기)
  • getSize() : 한 페이지 당 데이터 개수
  • getNumber() : 현재 페이지 번호
  • hasNext() : 다음 페이지가 존재하는지
  • hasPrevious() : 이전 페이지가 존재하는지

PageImpl<T>Page<T> 인터페이스를 구현한 구체적 클래스라 생각하면 된다.

PageImpl<T> 기본 사용법

JDBC에서 PageImpl<T>를 사용하여 페이징 데이터 변환

public Page<Todo> findAll(Pageable pageable) {
    int pageSize = pageable.getPageSize();
    int pageNumber = pageable.getPageNumber();
    int offset = pageNumber * pageSize;

    // 1. 페이징된 데이터 조회
    String sql = "SELECT id, contents, user_id, password, create_date, edit_date FROM todos LIMIT ? OFFSET ?";
    List<Todo> todos = jdbcTemplate.query(sql, new Object[]{pageSize, offset}, (rs, rowNum) -> new Todo(
            rs.getLong("id"),
            rs.getString("contents"),
            rs.getLong("user_id"),
            rs.getString("password"),
            rs.getTimestamp("create_date").toLocalDateTime(),
            rs.getTimestamp("edit_date").toLocalDateTime()
    ));

    // 2. 전체 데이터 개수 조회
    String countSql = "SELECT COUNT(*) FROM todos";
    int totalElements = jdbcTemplate.queryForObject(countSql, Integer.class);

    // 3. `PageImpl<T>`로 반환
    return new PageImpl<>(todos, pageable, totalElements);
}

위 코드는 3. Repository에서 Page<T> 처리 부분을 한줄에, 그리고 User와 join하지 않은 상태를 처리한 코드이다.
위와 같이 PageImpl<>을 통해 Page<> 객체를 생성해 반환해주는 모습을 볼 수 있다.

PageImpl<T> vs Page<T> 차이점

개념Page<T>PageImpl<T>
인터페이스 or 클래스인터페이스구현 클래스
Spring Data JPA 필요 여부필요불필요 (직접 생성 가능)
직접 생성 가능 여부직접 생성 불가직접 생성 가능
JDBC에서 사용 가능 여부불가능가능

즉, PageImpl<T>Page<T>를 직접 구현할 때 필요한 클래스이다.

profile
문제 해결을 좋아하는 개발자 입니다 :)

0개의 댓글