Page<T>
란 Spring Data에서 제공하는 페이징 처리를 위한 인터페이스로서, List<T>
와 비슷하지만 총 데이터 개수, 페이지 번호, 전체 페이지 수 등의 정보를 추가적으로 제공하는 것이 특징.
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);
Page<T>
)가 반환된다.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단으로 넘겨주어 처리해준다.
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단으로 넘겨주어 데이터를 처리한 뒤, 반환해준다.
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 짱짱!)
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은 Spring Data에서 페이징 요청을 처리하는 객체로서 페이징을 수행할 때 페이지 번호(page), 페이지 크기(size), 정렬(sort)등의 정보를 포함함.
getPageNumber()
: 현재 페이지의 번호getPageSize()
: 한 페이지당 데이터 개수getSort()
: 정렬 정보getOffset()
: 현재 페이지에서 시작하는 데이터의 offset 값(page * size)isPaged()
: 페이징이 적용되었는지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<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> 기본 사용법
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>
를 직접 구현할 때 필요한 클래스이다.