페이징 처리 기능

유수민·2022년 7월 11일
0

FoodMall 프로젝트

목록 보기
11/14
post-custom-banner

📌페이징 처리 기능이 왜 필요한가?

페이징이란 주로 상품목록의 젤 밑에 존재하는 페이지들을 볼 수 있게 하는 기능이다. 이것이 왜 필요할까?

📖1) 가독성의 문제

한 페이지에 모든 상품들이 다 나오면 가독성을 떨어지게 만든다. 예를 들어 500개의 상품이 있다고 치자. 500개의 상품들이 한페이지에 다 나온다면 너무 많아 특정 상품을 찾기 위해서는 스크롤을 엄청 내려서 찾아야 할 것이다. 너무나 비효율적이다.

📖2) 자원 낭비

너무 많은 데이터를 한꺼번에 한페이지에 다 출력하려고 한다면 시스템의 자원을 너무 많이 사용한다. DB에도 부담이 클것이고, 모든 데이터를 로드하는데 많은 시간을 소모하게 한다.이 문제들을 해결하기 위해서 페이징 처리를 해주는 것이다.

📌페이징 처리의 방법

Spring Data JPA에서는 기본적으로 페이징을 위해 Page와 Pageable를 가지고 있다. Pageable이라는 객체는 페이징하는 방법을 기술해놓은 클래스(인터페이스)라고 보면 되고, Page 객체는 실체로 페이징으로 잘려진 객체들을 담고 있는 객체라고 생각하면 된다.

또한, Pageable은 내부에 정렬기능의 sort를 포함한다. 따라서 이 프로젝트에서도 sort에 따라 페이지를 만들어내도록 구성되어 있다. 이 프로젝트에는 페이징 기능이 있어야 할 페이지들이 많다. ex) 신상품, 베스트, 이벤트의 상품목록,각 카테고리의 상품목록

우선, 이 페이지들이 공통으로 가지고 있는 페이징 기능의 큰 틀만을 먼저 설명하도록 하겠다.

📌페이징 기능 구현하기(상품목록 페이지)

📖1. DTO 만들기

만드는 과정을 설명하기 전에 내가 가졌던 의문을 하나 던져본다. 기존에 domain 폴더에 상품목록의 entity를 만들어놓았다. 그런데 왜 DTO를 또 따로 만들어야만 하는가? 원래 있던 entity를 활용하면 안될까? 궁금해서 이에 대하 설명은 페이지 따로 만들었다. 아래 사진은 product의 Entity와 goodList의 DTO이다.

📖2. PageRequest 클래스 만들기

Spring Data JPA에서는 기본적으로 페이징을 위해 Page라는 객체와 Pageable이라는 인터페이스를 이용한다. 먼저, Pageable 에 대한 설명을 해보도록 하겠다. 스프링은 페이징 관련 쿼리 파라미터를 Pageable 구현체인  PageRequest로 매핑하여 전달한다. 즉, PageRequest에 의해 Pageable에 페이징 정보가 담겨 객체화 된다. 사실 이때 든 생각은 왜 이렇게 해야하지? 왜 PageRequest가 필요하지? 그냥 Pageable을 사용하면 안되나?라는 의문점이 많았다.

📚조사결과

조사해본 결과, Pageable 인터페이스는 페이지 처리에 필요한 정보를 전달하는 용도의 타입으로 인터페이스이기 때문에 실제 객체를 생성할 때는 구현체인 PageRequest라는 클래스를 사용해야만 한다는 것을 알게 되었다.

📚PageRequest의 사용

PageRequest는 몇 페이지, 한 페이지의 사이즈, Sorting 방법(Option)을 가지고 있고, Repository에 Paging을 요청할 때 사용된다. PageRequest 클래스의 생성자는 protected로 되어 있어 new를 이용할수 없기 때문에 객체를 생성하기 위해서는 static인 of()를 이용해서 처리해야 하고, page, size, sort라는 정보를 이용해서 객체를 생성한다. 그리고, Service에서 sort조건을 넣어주어서 PageRequest클래스의 getPageableforGood를 호출하여 pageable객체를 생성하도록 해주었다.(빨간줄)

즉, 클라이언트 쪽이 요구하는 조건(페이징, 정렬)의 dto를 pageable 객체로 변환한것이다.여기서 pageRequest클래스에서 한가지 더 언급하자면, 페이지의 맨처음 인덱스는 0부터 시작이라는 것이다. 따라서, 원래는 코드의 파란박스인 this.page를 나중에 헷갈릴까봐 1로 하고 return값에 page -1이라 넣어두었다.

page : 현재 페이지, 0부터 시작한다.

size : 한 페이지에 노출할 데이터 건수

sort : 정렬 조건을 정의한다.

참고)https://velog.io/@hermaeus/8.페이징정렬하기

📖3. Page 객체

Service를 보면, 클라이언트의 요청값을 담은 pageable객체를 repository의 파라미터로 넘기고 얻은 데이터의 결과를 Page객체인 result로 저장되게 된다.

📖4.Service와 Repository

위에서 설명하다보니 중간중간에 자꾸 Service와 Repository가 언급된다. 흐름을 잠깐 보자.

📚 코드 흐름

(1) 클라이언트로부터 요청 받음(DTO)
: 클라이언트 쪽이 요구하는 페이징 조건의 정보를 담은 pageRequestDTO를 받는다. ( service는 DTO 형태의 데이터만 받을 수 있다)

(2) 받은 pageRequestDTO에 정렬 조건을 넣어 pageable객체를 만든다.

(3) JPA처리와 DTO → Entity처리 : result 객체

JPA처리를 위해서는 Entity 타입의 객체로 변환해야 한다. repository에서 Pageable 변수를 추가하면 Page타입을 반환형으로 사용할 수 있다. Page< Product>로 되어 있는 이 Page타입으로 반환되게 함으로써 Entity 타입의 객체를 얻게 되는 것이다.
(리턴 타입을 Page<>로 받으려면 Pageable 파라미터를 받아야한다!!)
result 객체로 그 JPA처리 결과를 담아두었다.


#Repository
JPA 처리를 위해서는 repository를 활용해야한다. 페이지 처리를 위해 Repository는 JpaRepository를 상속받도록 되어 있다. JpaRepository는 내부적으로 PagingAndSortingRepository를 상속하면 CrudRepository도 같이 상속받는다.

따라서 PagingAndSortingRepository 클래스를 상속 받고 있기 때문에 페이징과 정렬를 활용할 수 있고, CrudRepository를 상속 받아서 create, read, update, delete를 할 수 있다는 특징을 가지고 있다.


(4) Entity → DTO 를 하는 객체 만들어주기 : fn 객체

쿼리문 후 다시 데이터 전송하려면(controller로 데이터 보내려면) dto로 변경되어야 한다. Entity를 DTO로 바꾸기 위해서 Function 사용하여서 객체를 만들어주었다. Function<T,R> 인터페이스는 T를 인수로 받아 R객체를 반환하는 메서드 apply를 정의한다. 즉, 여기서는 T가 Product고 R이 GoodListDTO이기 때문에 Entity를 DTO로 바꾼다는 말이 된다.
아래 그림에서 보면 entity → entitytoGoodListDTO(entity)라고 되어 있는데, 여기서 entitytoGoodListDTO(entity) 메소드를 이용해 DTO로 바꿔주도록 해주었다.

그리고 이것을 객체 fn에 담도록 하였다.

(5) PageResult클래스에 쿼리문의 결과 데이터를 담은 result와 DTO형태의 fn 객체를 파라미터로 담아서 반환한다.

📖5. PageResult 클래스 만들기

PageResult 클래스는 Page를 형성하기위한 클래스이다. PageRequest 클래스에는 단순히 몇 페이지, 한 페이지의 사이즈, Sorting 방법등의 재료만을 구성하였다면, PageResult클래스에는 현재페이지, 이전페이지, 다음페이지, 총페이지 등 직접적으로 page를 형성하는 클래스이다.

PageResult 클래스는 2가지 메서드를 담고 있는데

📚(1) 첫번째는 Page< Entity>를 화면에서 사용하기 쉬운 DTO리스트로 변환하는 메서드로 Service에서 Entity 객체와 function 객체를 파라마터로 담아 return 값으로 이 메서드를 반환하도록 되어 있다.

result는 Page객체로 기본적으로 총페이지수를 받을 수 있다 = getTotalPages() dtoList는 Page객체인 Entity를 DTO로 변환하여 리스트에 담는 변수이다.

좀 더 자세히 설명해보자면,

result.stream() → 스트림을 생성
.map(Function<T,R>) → t에서 r로 변환. map은 Function을 인자로 받는다. 여기서는 fn
.collect(Collectors.toList()) → 리스트로 결과를 가져온다.

이렇게 dtoList와 totalPage 변수를 다 만들고 나면, 두번째 메서드를 호출한다.

📚(2) 두번째 메서드는 말그대로 pageList를 만드는 makePageList이다.

이 메서드는 page객체인 result를 파라미터로 받는 것이 아닌 page에 대한 요청 정보를 담고 있는 Pageable  타입의 객체를 받아오도록 하였다. 그리고, 페이지를 구성하는데 필요한 변수들을 계산하도록 되어 있다.

📖6. Controller

기본적으로 상품목록에 대한 페이징 처리에 대해 설명하기로 했으니 그것을 기준으로 우선 큰 그림만을 설명하자면, 요청한 정렬 기준에 따라 아래사진처럼 받아와서 goodList에 뿌려주도록 하였다.

사실 어떤 페이지를 요청하느냐에 따라 Controller가 많이 바뀌는데 이 점에 대해서는 따로 게시물을 작성하여 설명하도록 하겠다.

📖7. View

📚1) 상품 데이터

정렬에 따라 화면에 뿌려주기에 위해, 상품들의 데이터를 담은 goodLists를 ${ }로 받아와야 한다.

다만, 주의할 점은 goodLists 에는 상품들의 데이터 뿐만 아니라 페이지에 대한 정보도 담겨 있기 때문에 단순히 goodLists가 아닌 상품의 데이터가 담긴 dtoList를 정확히 가져와야 한다. 따라서, goodList.dtoList를 가져와한다.

📚2) 페이지 처리

아래 숫자에 대한 이야기를 해보자.

(1) ‘이전’과 ‘다음’ 의 경우

5개의 숫자가 넘어가면 다음이라는 숫자가 나오도록 되어 있고, 그 이후는 앞에 이전이라는 글자가 나오도록 했다.
pageResult클래스의 makePageList메서드에서 각각의 변수가 true가 나왔을 경우에만 화면 (goodList.html)에 보이도록 되어 있다.
→ th:if=${goodList.prev}, th:if={goodList.next}

(이전을 말하는 prev는 start >1 일때 true가 나오고, 다음을 말하는 next는 totalPage > tempEnd일 때만 true로 된다.)

(2) 번호

코드에서 주목해야 할점은 현재 위치한 번호의 클래스의 class만 이름이 active가 덧붙여져서 그 부분만 css 적용이 되도록 한것이다.

예를 들어, 현재 1페이지면

해당 번호만 class이름이 active가 덧붙이게 되어 css 적용이 달라지게 하였다.

📚3) 정렬 처리

정렬에 따라 색 바꾸는 작업도 필요하다.
즉, 신상품을 눌렀을 경우 신상품에, 인기순을 눌렀을 경우 인기순에만 색깔이 칠해지게 하는 것이다.

사실 이것은 간단하다.

컨트롤러에서 정렬을 sort로 뿌려진것을 html에서 받아와서

script에서 번호에 따라 특정 부분만 색이 나오게 하고 나머지는 다 검정으로 나오도록 바꿔주기만 하면 된다.

profile
배우는 것이 즐겁다!
post-custom-banner

0개의 댓글