1️⃣ 요구사항
먼저 요구사항부터 확인 해보자 !
1) 설명
- 많은 양의 데이터를 효율적으로 표시하기 위해 데이터를 여러 페이지로 나눕니다.
- 페이지 번호와 페이지 크기를 쿼리 파라미터로 전달하여 요청하는 항목을 나타냅니다.
- 전달받은 페이지 번호와 크기를 기준으로 쿼리를 작성하여 필요한 데이터만을 조회하고 반환
2) 조건
- 등록된 일정 목록을 페이지 번호와 크기를 기준으로 모두 조회
- 조회한 일정 목록에는 작성자 이름이 포함
- 범위를 넘어선 페이지를 요청하는 경우 빈 배열을 반환
- Paging 객체를 활용할 수 있음
3) 참고 사이트
참고 사이트 : Pagination With JDBC | Baeldung
참고 블로그 : [Spring] Spring DATA JDBC Pagination API
4) Offset 활용
참고 사이트를 번역 해보면 아래와 같다.
SELECT
쿼리와 함께 LIMIT
및 OFFSET
을 사용하여 정의된 결과 크기를 반환할 수 있습니다.
LIMIT
절은 반환하려는 행 수를 가져오는 반면 OFFSET
절은 쿼리 결과에서 정의된 행 수를 건너뜁니다.
그런 다음 OFFSET
위치를 제어하여 쿼리에 페이지를 매길 수 있습니다.
아래 논리에서는 LIMIT
를 pageSize
로 정의하고 offset
을 레코드 읽기를 위한 시작 위치로 정의했습니다.
ResultSet readPageWithLimitAndOffset(Connection connection, int offset, int pageSize) throws SQLException {
String sql = """
SELECT * FROM employees
LIMIT ? OFFSET ?
""";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, pageSize);
preparedStatement.setInt(2, offset);
return preparedStatement.executeQuery();
}
- 일단 대충 해석해보면, Paging
을 만들려면, 우리는 SQL
문에서 Limit
와 Offset
을 활용하면 된다는 것이다.
- 여기서 중요한 게, 우리는 Offset
을 넘겨주는 게 아니라 PageSize
와 Page Number
인 것을 인지해야한다.
- Offset
부터 Limit
수 만큼 조회하는 것이다.
- 그러니까, 만약에 Limit 10, Offset 20
이면 20 부터 시작해서 10개 출력을 하는 것이다. 20~30 조회를 한다는 것
- 조금 더 쉽게 보여주자면, 아래와 같다.
- 위에를 이해했다면 이제 아래 부분을 이해해야한다 !
- 나중에 SQL
문에 offset = PageSize \* (PageNumber - 1)
이런식으로 계산할 것이다 !
2️⃣ Page 객체
- 일단 페이징이라는 객체를 만들어서, 활용하라고 해서 Page
라는 객체를 만들고자 했다.
- 이게 다른 참고사이트들을 확인해보니, Page<T>
이런 식으로 제네레이션 타입으로 받는 것을 확인했다.
- 일단 나는 하나의 ResponseDTO
타입으로 받을 거긴 한데, 재사용성과 유지보수성을 생각하면 T
타입으로 받는게 맞는 거 같아서 T
로 선언했 다.
@Getter
@AllArgsConstructor
public class Page<T> {
private List<T> planList;
private int pageNumber;
private int pageSize;
}
- 이번 요구사항에서는 페이지 번호와 크기, 그리고 일정 목록만 필요하기 때문에 추가적인 필드는 선언하지 않았다.
( 원래는 전체 페이지 수와, 전체 데이터 개수를 포함하여야 한다. JPA 에서는 더 편하게 이용할 수 있다고 한다. )
3️⃣ ResponseDto
@Getter
@AllArgsConstructor
public class PageResponseDto {
private Long id;
private String task;
private Long authorId;
private String name;
private String created;
private String updated;
}
- 요구사항 내에 작성자의 이름도 포함하라 하여서, 이때 당시에는 name
을 포함한 ResponseDTO
가 없어서 Page
만을 위한 PageResponseDto
를 따로 만들어 주었다.
4️⃣ Controller
@GetMapping("/page")
public ResponseEntity<List<PageResponseDto>> page(
@RequestParam @Min(1) int pageNumber,
@RequestParam @Min(1) int pageSize
) {
return new ResponseEntity<>(scheduleService.getPlan(pageNumber,pageSize).getPlanList(),HttpStatus.OK);
}
- Validated
도 설정해주었다. 최소 1은 입력하게끔 하였다.
- 서비스의 getPlan()
를 호출해서, Page
객체의 List
를 get
한걸 볼 수 있다.
5️⃣ Service
public Page<PageResponseDto> getPlan(int pageNumber, int pageSize) {
return scheduleRepository.getPlan(pageNumber, pageSize);
}
- 우리는 Page
객체에 담아서 보내줄 것이기 때문에 반환값이 Page
인것을 확인 할 수 있다 !
6️⃣ Repository
// 일정 페이징 조회
public Page<PageResponseDto> getPlan(int pageNumber, int pageSize) {
int offset = (pageNumber - 1) * pageSize;
List<PageResponseDto> result = jdbcTemplate.query("SELECT p.id, p.task, a.id, a.name ,p.created, p.updated " +
"FROM plan p JOIN author a on a.id = p.authorId " +
"ORDER BY p.id " + "LIMIT ? OFFSET ?", PageRowMapper(), pageSize, offset);
return new Page<>(result, pageNumber, pageSize);
}
- 우리는 이렇게 sql 문과 이를 jdbcTemplate.query
를 이용해서 mapper
이용 , ?
삽입만 하면 가능하다.
- 이게 다른 사람들은 해당 문제에 어떻게 접근했나 싶었는데,
- jdbcTemplate.query
를 이용하는 거이 아니라, PreparedStatement
를 이용해서 sql
을 준비하고, 그 후 setInt()
를 하여 ?
에 값을 넣었다. 그러고 while
문이나 for
문을 통해서 매핑을 하였다. 우리로 따지면 RowMapper()
를 while
로 구현한 거 같았다.
private RowMapper<PageResponseDto> PageRowMapper() {
return new RowMapper<PageResponseDto>() {
@Override
public PageResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
return new PageResponseDto(
rs.getLong("p.id"),
rs.getString("p.task"),
rs.getLong("a.id"),
rs.getString("a.name"),
rs.getString("p.created"),
rs.getString("p.updated"));
}
};
}
- 이걸 해석하면, SELECT p.id, p.task, a.id, a.name ,p.created, p.updated
에 있는 column
을 dto
와 매핑 시키는 것이다. 이렇게 하나씩 연결해주면 하나의 리스트에 값이 담기게 되는 것이다.
7️⃣ Test
- 실제로 잘 작동한다 : > !
- 일단 혹시 필요할 까봐 'ㅡ' github 링크를 올려두었다.
sinyoung0403/ScheduleApp-Server
GitHub - sinyoung0403/ScheduleApp-Server
Contribute to sinyoung0403/ScheduleApp-Server development by creating an account on GitHub.
github.com
✅ 오늘의 회고
- 이게 이제 JPA 로 가면 사용하지 않을 방식이 될 거 같다.
- 일단은 구현을 목적으로 만들긴 했는데, 잘 사용하지 않는 방식이라 : > ,,, 도움이 될련가 모르겠다.
- 얼떨결에 구현은 끝나버렸다. 내일은 Readme 랑 API 문서 재정리를 해야겠다 👻
'백엔드 부트캠프 > TIL' 카테고리의 다른 글
[내일배움캠프Spring-28일차] Spring DI/IoC (1) | 2025.03.27 |
---|---|
[내일배움캠프Spring-27일차] 일정 관리 앱 개발 회고 (0) | 2025.03.26 |
[내일배움캠프Spring-25일차] 일정 관리 과제 도전 Lv3 (0) | 2025.03.24 |
[내일배움캠프Spring-24일차] 일정 관리 과제 필수 기능 구현 (2) | 2025.03.21 |
[내일배움캠프Spring-23일차] JDBC 를 이용한 CRUD 이해하기 [MySql] (0) | 2025.03.20 |