프로젝트를 진행하면서 회원의 포인트 적립 및 사용 상세 이력을 조회하는 요구사항을 개발하기 위해서 Spring Data JPA를 이용한 페이징 처리를 구현했다.
화면에서 커서 스크롤시 데이터를 호출해오는 무한 스크롤 방식의 페이징을 위해 커서 페이징 방식으로 구현을 진행했다.
동작 방식을 간단히 정리하면,
- 프론트에서 URI로 회원의 최신 포인트 상세 이력 아이디와 조회 건수를 담아 호출한다.
- 헤더에 담겨진 JWT 토큰을 파싱해 회원 아이디를 추출한다.
- 회원 아이디를 통해 포인트 상세이력을 페이징 해서 조회한 후 가공해 반환한다.
Controller 코드를 확인해보자.
// PointController
@GetMapping(value = "/details")
@Operation(summary = "포인트 상세 이력 조회", description = "회원의 포인트 상세이력을 조회한다.(10개 페이징)")
public ResponseEntity<?> searchPointDetails(@RequestParam(name = "pointId") Long pointId, @RequestParam(name = "size") int size, HttpServletRequest servletRequest) {
log.info(" >> [Nbbang Point Service] 포인트 상세 이력 조회");
try {
// 1. 회원 아이디 파싱
String memberId = jwtUtil.getUserid(servletRequest.getHeader("Authorization").substring(7));
// 2. 포인트 상세이력 조회
List<PointDTO> findPoint = pointService.findPointDetails(memberId, pointId, size);
// 3. 조회한 내용 리턴
return new ResponseEntity<>(PointDetailsResponse.create(memberId, findPoint, true, "회원의 포인트 상세이력 조회에 성공했습니다."), HttpStatus.OK);
} catch (NoSuchMemberException e) {
log.info(" >> [Nbbang Point Controller - searchMemberPoint] : " + e.getMessage());
// 조회 실패 시 에외 처리
return new ResponseEntity<>(CommonResponse.create(false, e.getMessage()), HttpStatus.OK);
}
}
해당 URI의 쿼리 파라미터로 Unique한 Long 타입의 포인트 아이디와 한 번에 몇 건을 불러올지에 대한 Size 값을 받는다.
포인트 상세이력을 조회하는 Service Layer 로직을 확인하자.
// PointServiceImpl
/**
* 회원 아이디를 이용해 회원의 포인트 사용 상세 이력을 10건씩 조회한다.
*
* @param memberId JWT 토큰을 파싱한 세션 회원 아이디
* @param pointId Long 타입의 Unique한 포인트 상세이력 아이디
* @param size 포인트 상세이력을 호출해올 개수
* @return PointDTO 조회환 포인트 상세이력 리스트
*/
@Override
public List<PointDTO> findPointDetails(String memberId, Long pointId, int size) {
// 1. 회원 아이디를 통해 회원 객체 찾기
Member findMember = Optional.ofNullable(memberRepository.findByMemberId(memberId))
.orElseThrow(() -> new NoSuchMemberException("회원이 존재하지 않습니다.", NbbangException.NOT_FOUND_MEMBER));
// 2. 포인트 이력 리스트 조회하기
Slice<Point> findPoint = pointRepository.findByIdLessThanAndMemberOrderByIdDesc(pointId, findMember, PageRequest.of(0, size)); // 무한 스크롤 방식, 페이지는 0으로 고정
return PointDTO.createList(findMember, findPoint);
}
JPA에서 페이징은 Pageable 인터페이스를 구현함으로 써 간단하게 사용이 가능하다.
Pageable 인터페이스는 PageRequest.of(int page, int size)메소드를 통해 구현이 가능하다.
- page : 음수 값이 아니면서 0 이상의 시작하는 페이지의 인덱스
- size : 한 페이지당 반환해줄 데이터의 개수
구현한 포인트 서비스 에서는 무한 스크롤 방식을 채택하고, 유니크한 포인트 아이디를 이용해 정렬한 데이터에서 입력으로 들어온 포인트 아이디보다 작은 값의 아이디를 가진 이력을 조회하기 때문에 PageRequest.of의 page 파라미터는 0으로 고정하였다.
마지막으로 Repository 코드를 확인해보자 .
@Repository
public interface PointRepository extends JpaRepository<Point, Long> {
// 데이터 불러오기 페이징 처리로 구현 (10개)
// Page<Point> findByIdLessThanAndMemberOrderByIdDesc(Long id, Member member, Pageable pageable);
Slice<Point> findByIdLessThanAndMemberOrderByIdDesc(Long id, Member member, Pageable pageable);
}
Spring Data JPA는 페이징을 통해 불러온 객체들을 List, Slice, Page 타입으로 반환받는 것이 가능하다.
JpaRepository를 이용해서 쿼리메소드를 생성한다.
- findByLessThan : 포인트 아이디보다 작은 아이디 값 (where point_id < 파라미터)
- AndMember : 회원 아이디와 동일한 값 (Member 엔티티의 Id는 memberId) (where member_id = 파라미터)
- OrderByIdDesc : 포인트 아이디를 이용해 내림차순 정렬 (order by point_id desc)
페이지 값을 주지 않고, 유니크한 포인트 아이디를 이용해 동적으로 쿼리의 내용을 변경하기 때문에 반드시 정렬이 필요하다.
페이징 처리를 위한 구현이 모두 마무리되었다.
단위 테스트 코드를 통해 제대로 데이터를 호출해오는지 확인해보자.
현재 데이터베이스에 “test”란 아이디로 상세 이력이 저장되어있다.
PageReqeust.of메소드를 이용해 Pageable을 구현한 후 포인트 아이디를 넣어 호출해보자.
테스트 코드에서는 22번 아이디보다 작은 포인트 상세 이력 3건을 호출했다.
간단한 확인을 위해 반환한 데이터의 사이즈와 3건의 포인트 아이디를 비교해보았다.
@Test
@DisplayName("포인트 레포지토리 : 포인트 상세이력 불러오기 성공")
void 포인트_상세이력_불러오기_성공() {
// given
Member findMember = testMember();
Pageable pageable = PageRequest.of(0, 3);
// when
Slice<Point> findPointDetail = pointRepository.findByIdLessThanAndMemberOrderByIdDesc(22L, findMember, pageable);
// then
assertEquals(findPointDetail.getSize(),3);
assertEquals(findPointDetail.getContent().get(0).getId(), 21);
assertEquals(findPointDetail.getContent().get(1).getId(), 20);
assertEquals(findPointDetail.getContent().get(2).getId(), 19);
}
제대로 잘 데이터를 호출해오는 것을 확인할 수 있다.
'Backend > Spring Boot' 카테고리의 다른 글
싱글톤 스코프, 프로토타입 스코프 (0) | 2022.05.03 |
---|---|
Spring Data JPA의 Page와 Slice (0) | 2022.05.02 |
Spring Cloud Config Server를 Private Repository와 연동 (0) | 2022.04.28 |
Spring Cloud Config Server/Client 설정 (0) | 2022.04.28 |
Swagger를 이용한 API 문서화 (0) | 2022.04.28 |