-
Pageable 사용하기JPA 2020. 2. 8. 12:13
개요
JPA를 이용해서 Paging API를 만들어 보도록 하겠습니다. 페이징 처리는 거의 모든 웹 개발에서 사용하고 있습니다. 그렇게 복잡하고 어려운 구현은 아니나 실제 쿼리로 작성할 때는 상당히 번거로운 작업이 됩니다. 또 데이터베이스마다 페이징 쿼리가 조금씩 다르다는 점도 복잡도를 높이는 요인 중 하나입니다.
Paging이란?
DB에 저장된 Entity들을 페이지로 나누는 것이다.
예를들어, DB에 21개의 게시판이 작성되어있다.
프론트에서 "DB에 있는 게시판을 5개씩 분류해서, 두 번째 파트를 줘!" 라고 요청한다.
그러면 백엔드에서는 5개씩 분류하고, 분류된 게시판들의 두 번째 파트를 프론트에게 넘겨준다.
위 상황과 같이, 일정 갯수만큼 분류하고, 분류된 부분들 중 어떤 부분을 보내주는 것이 Paging이다.- Paging Input
- page : 검색을 원하는 페이지 번호입니다.
- size : 한 페이지의 조회할 게시물 개수를 나타냅니다.
- sort : 정렬 방식을 나타냅니다. ex) 속성명, DESC or ASC
- BoardController
@RequestMapping(value = "", method = RequestMethod.GET) public @ResponseBody BusinessResultVo getBoardList(HttpServletRequest request, Pageable pageable, @RequestParam(value="user_key", required = false) Long userKey) throws CustomException { BusinessResultVo businessResultVo = new BusinessResultVo(); try { String nextCorsor = null; Optional<UserVo> user = userService.getUser(userKey); if(user.isPresent() == false) { throw new InvalidUserkeyUnauthorizedException(statusVo.userkeyInvalidCode, "[BoardController.boardDelete] : {userKey = " + userKey + "}, errorMsg = " + statusVo.userkeyInvalidDesc); } Page<BoardVo> pageList = boardService.selectBoardList(user, pageable); List<BoardVo> boards = pageList.getContent(); if(!pageList.isLast()) { nextCorsor = String.valueOf(pageable.getPageNumber() + 1); } businessResultVo.addResult("next_corsor", nextCorsor); businessResultVo.addResult("board_list", boards); } catch (CustomException ex) { businessResultVo.setCode(ex.getCode()); businessResultVo.setDesc(ex.getDesc()); throw ex; } return businessResultVo; }
Response Body 부분에 Pageable 선언하여 요청을 페이징 처리에 대한 Input 값들을 받습니다.
Page<BoardVo> pageList = boardService.selectBoardList(user, pageable)
반환형은 Page<BoardVo> 형태로 선언하였고 게시판을 작성한 유저의 정보와 pageable 객체를 boardService 쪽으로 넘겨줍니다.
- BoardService
@Service public class BoardService { @Autowired private BoardRepository boardRepository; public Page<BoardVo> selectBoardList(Optional<UserVo> user, Pageable pageable) { return boardRepository.findAllByUser(user, pageable); } }
위와 동일하게 유저 정보와 pageable 객체를 boardRepository 쪽으로 넘겨줍니다.
- BoardRepository
public interface BoardRepository extends JpaRepository<BoardVo, Long>{ Page<BoardVo> findAllByUser(Optional<UserVo> user, Pageable pageable); }
BoardRepository에서는 쿼리 메서드(메서드 이름으로 쿼리 생성)를 사용하였습니다.
쿼리 메서드란, 규칙에 맞게 메서드 이름을 “잘“ 정의한다면 Spring Data JPA가 메서드 이름을 파싱하여 쿼리를 생성해내는 방식입니다.
findAllByUser 메소드의 내용은 아래와 같다.
method 기능 findAll() 전체 레코드 불러오기. 정렬(sort), 페이징(pageable) 가능 findBy로 시작 쿼리를 요청하는 메서드 임을 알림 Query 메소드에 포함할 수 있는 키워드는 다음과 같다.
메서드 이름 키워드
샘플
설명 And
findByEmailAndUserId(String email, String userId)
여러필드를 and 로 검색
Or
findByEmailOrUserId(String email, String userId)
여러필드를 or 로 검색 Between
findByCreatedAtBetween(Date fromDate, Date toDate)
필드의 두 값 사이에 있는 항목 검색
LessThan
findByAgeGraterThanEqual(int age)
작은 항목 검색
GreaterThanEqual
findByAgeGraterThanEqual(int age)
크거나 같은 항목 검색 Like
findByNameLike(String name)
like 검색
IsNull
findByJobIsNull()
null 인 항목 검색
In
findByJob(String … jobs)
여러 값중에 하나인 항목 검색 OrderBy
findByEmailOrderByNameAsc(String email)
검색 결과를 정렬하여 전달
BoardService에서 findAllByUser() 메서드를 호출하면 다음과 같은 쿼리가 출력됩니다.
select * from ( select boardvo0_.board_key as board_ke1_0_, boardvo0_.board_desc as board_de2_0_, boardvo0_.board_title as board_ti3_0_, boardvo0_.mod_date as mod_date4_0_, boardvo0_.reg_date as reg_date5_0_, boardvo0_.reg_user_key as reg_user6_0_ from rest_board boardvo0_ where boardvo0_.reg_user_key=? order by boardvo0_.mod_date desc ) where rownum <= ?
- Result
개선
위의 Pageable의 개선할 점이 있습니다. 우선 size에 대한 limit이 없습니다. 위의 API에서 size값을 200000을 넘기면 실제 데이터베이스 쿼리문이 200000을 조회할 수 있습니다. 그 밖에 page가 0 부터 시작하는 것들도 개선하는 것이 필요해 보입니다.
결론
DB에 접근하는 Repository는 인터페이스로 정의하여 JpaRepository를 상속받기만 하면, 애플리케이션이 실행 될 때 Spring Data JPA가 JpaRepository의 메서드를 구현합니다. 페이징 처리는 데이터베이스마다 페이징 쿼리가 다른 부분들은 신경 쓰지 않고 더 쉽게 구현할 수 있는 장점이 있습니다.
'JPA' 카테고리의 다른 글
02. JPA 시작 (0) 2021.01.17 01-3. JPA란 무엇인가? (0) 2021.01.14 01-2. JPA 소개 (2) 2020.12.29 01-1. JPA 소개 (0) 2020.12.01 JPA란 무엇인가? (0) 2019.12.22