한 주가 지나고 새로운 주가 찾아왔다.
내가 공부하고 있는 와중에도
세상은 돌아가고 있다.

누군가의 시간은 느리게 가고 누군가의 시간은 빠르게 갈 것 같다.
시간을 어떻게 써야 잘 썻지? 라는 물음이 요즘 자꾸 생긴다.

공부를 효율적으로 하기에는 의지가 가장 중요한 것 같다.
주말동안 여러가지 자극을 받으면서, 다시한번 공부에 대한 의지와
내가 목표로가진 내용을 상기하면서 이번주부터 다시 임해보려한다.

image


오늘도 Spring data JDBC를 연습해보는 날이다.
마지막으로 연습해보는 날이고
내일부터는 본격적으로 JPA에 대해 공부해보는 시간이다.

사실 JDBC사용 방법보다는 객체간 이동
Mapper, DTO, Service 등 로직 구현을 주로 연습한 것 같다.

Pagination

오늘 구현해볼 내용은 Pagination을 구현할 것이다.
한글로 페이지네이션이라 부른다.
페이시네이션이란?

image

간단한 예제로 구글을 통해 검색할 경우
우리는 해당 검색어에 대한 내용이 쭉나오고, 다음 내용은
페이지로 다음을 눌러 넘어가서 확인한다.

이에 해당하는 프로그램을 구현할 것이다.

구현

문제1. RequsetParam를 통해 page, size값을 받는다
-> page: 몇번 페이지, size: 표시 수량

문제2. 아래와 같이 데이터가 담긴 Json 객체를 반환
-> 조회한 데이터는 memberId 내림차순으로 정렬할 것

참고

  1. DB에는 20개의 데이터가 저장되어있다.

Page = 1, Size = 10 일 때의 예제

{
    "data": [
        {
            "memberId": 20,
            "email": "hgd20@gmail.com",
            "name": "홍길동20",
            "phone": "010-2020-2020"
        },
        {
            "memberId": 19,
            "email": "hgd19@gmail.com",
            "name": "홍길동19",
            "phone": "010-1919-1919"
        },
        {
            "memberId": 18,
            "email": "hgd18@gmail.com",
            "name": "홍길동18",
            "phone": "010-1818-1818"
        },
        {
            "memberId": 17,
            "email": "hgd17@gmail.com",
            "name": "홍길동17",
            "phone": "010-1717-1717"
        },
        {
            "memberId": 16,
            "email": "hgd16@gmail.com",
            "name": "홍길동16",
            "phone": "010-1616-1616"
        },
        {
            "memberId": 15,
            "email": "hgd15@gmail.com",
            "name": "홍길동15",
            "phone": "010-1515-1515"
        },
        {
            "memberId": 14,
            "email": "hgd14@gmail.com",
            "name": "홍길동14",
            "phone": "010-1414-1414"
        },
        {
            "memberId": 13,
            "email": "hgd13@gmail.com",
            "name": "홍길동13",
            "phone": "010-1313-1313"
        },
        {
            "memberId": 12,
            "email": "hgd12@gmail.com",
            "name": "홍길동12",
            "phone": "010-1212-1212"
        },
        {
            "memberId": 11,
            "email": "hgd11@gmail.com",
            "name": "홍길동11",
            "phone": "010-1111-1111"
        }
    ],
    "pageInfo": {
        "page": 1,
        "size": 10,
        "totalElements": 20,
        "totalPages": 2
    }
}

DB에 20개의 데이터가 MEMBER Table에 저장이 되어있고
우리가 해야하는 것은 page, size의 값을 받아
해당 객체를 조립하여 JSON으로 반환해주어야한다.

그렇게 생각하여 나는 크게 6가지 과정으로 프로그램을 진행했다.

  1. 클라이언트 측 에서 데이터 요청 받은데이터를 맵핑한 객체를 만듬
  2. Service에서 Member 전체 조회한 후 필요한 데이터만 정리해 객체로 만듬
  3. 1,2번에서 만든 객체로 ResponseDto 맵핑함.
  4. 3번에서 맵핑하여 만든 전체 List 데이터를 JSON으로 응답

해당 과정을 통해서 프로그램을 작성했고, 실제로 Pagination을 하기위해
Spring에서 도움음 주는 클래스 Page, Pageable, PageRequest를 이용해
자동으로 Pagination을 해줄 수 도 있다.

우선 나는 위방법을 안쓰고 직접 코드로 정리해서 보내는 방법을 택했다.

@GetMapping
public ResponseEntity getMembers(@RequestParam("page") @Positive int page,
                                 @RequestParam("size") @Positive int size) {
    //PageInfo 데이터 맵핑
    MemberPageInfoDto memberPageInfoDto = mapper.dataToMemberPageInfoDto(page, size);

    //Member 전체조회 서비스 로직 실행
    List<Member> members = memberService.findMembers(memberPageInfoDto);

    //객체들 ResponsePageDto Mapping
    MemberResponsePageDto response = mapper.membersToMemberResponsePageDto(members,memberPageInfoDto);

    return new ResponseEntity<>(response, HttpStatus.OK);
}

우선 Controller의 getMembers를 작성.
0이아닌 양의정수만 포함한 데이터로 page,size를 받을 수 있다.

여기서 첫번째 줄 PageInfo에 데이터 맵핑 하는 클래스를 만들고
Mapper를 구현하여 데이터를 전달했다.

default MemberPageInfoDto dataToMemberPageInfoDto(int page, int size){
    return new MemberPageInfoDto(page, size,0,0);
};
default MemberResponsePageDto membersToMemberResponsePageDto(List<Member> members, MemberPageInfoDto memberPageInfoDto){
    MemberResponsePageDto memberResponsePageDtos = new MemberResponsePageDto(members,memberPageInfoDto);

    return memberResponsePageDtos;
};
@Getter
@Setter
@AllArgsConstructor
public class MemberPageInfoDto {
    int page;
    int size;
    int totalElements;
    int totalPages;
}

page, size를 이용해 나머지 값이 0인 객체를 만들었다.
그리고 아래있는 메서드는 응답할때 조립을 해주는 메서드이다.
인자로는 Member를 List화한 데이터와 방금 위에서 전달한
MemberPageInfoDto객체를 받는다.
이 두개의 데이터를 받아 최종적으로

@Getter
@AllArgsConstructor
public class MemberResponsePageDto {
    List<Member> data;
    MemberPageInfoDto pageInfo;
}

이 응답클래스에 데이터를 담아서 반환하게 되어진다.

이렇게 Mapper와 응답데이터 클래스를 만들었고
이제 데이터베이스에서 Member 클래스의 데이터를 조회해
우리가 원하는 page, size의 맞춰서 Member List를 반환해줘야한다.

public List<Member> findMembers(MemberPageInfoDto memberPageInfoDto) {
    // member 전체 내림차순 정렬 조회
    List<Member> members =  memberRepository.findAllByOrderByMemberIdDesc();

    // Pagenation Service 로직, 정리된 Member List만 반환
    return executionPagenation(members, memberPageInfoDto);
}

public List<Member> executionPagenation(List<Member> members, MemberPageInfoDto pageInfo){
    // page, size 가져오기
    int page = pageInfo.getPage();
    int size = pageInfo.getSize();

    // members 수량 파악 후 TotalElements, TotalPages Data 추가
    pageInfo.setTotalElements(members.size());
    if(members.size()<=size){
        pageInfo.setTotalPages(1);
    } else {
    int memberSize = (int)members.size()/size;
    int memberLeft = members.size()%size;
    if(memberLeft!=0) memberSize++;
        pageInfo.setTotalPages(memberSize);
    }

    // page, size만큼의 Data만 담은 List 반환
    List<Member> membersMakeUp = new ArrayList<>();
    int remainPage = members.size() % size;
    int from = (page-1)*size;
    int to = (remainPage!=0 && pageInfo.getTotalPages()==page) ? from+remainPage : from+size;

    for(int i=from;i<to;i++){
        membersMakeUp.add(members.get(i));
    }
    
    return membersMakeUp;
}
        
public interface MemberRepository extends CrudRepository<Member, Long> {
    List<Member> findAllByOrderByMemberIdDesc();
}

이렇게 Service 로직을 구현했다.
처음에 맵핑했던 PageInfo 객체에 나머지 값을을
setter를 통해서 데이터를 입력해준 다음에

Member 테이블의 데이터를 내림차순으로 조회하여
받은 데이터를 page와 size값으로 원하는 페이지에
수량별로 표시가 될 수 있게끔 프로그래밍하여
Member List를 최종적으로 반환하게 해주었다.

이렇게 되면 최종적으로 페이지에 맞는 Member List 객체와
PageInfo 객체를 가지고 있기 때문에 최종적으로
MemberResponsePageDto 클래스 타입으로 맵핑해서 응답데이터를
ResponseEntity를 통해 반환하도록 하면 !!

image

page=1, size=2 로 하였을 경우의 반환된 Json의 모습.
totalElements = 20개의 데이터
totalPages = 10개의 페이지.

정상적으로 작동하는 모습이 확인 가능하다.
Spring Pagination API를 이용해 작성하는 방법을 참고해보자
해당 방법은 선배기수에서 Spring API를 이용해 간단하게 구현한 모습이다.


오늘의 커피량: ☕️ ☕️ ☕️
오늘의 점심: 라면