TIL - 0714 (Dto 반환 페이징)

박연주·2022년 7월 14일
0

TIL

  • 프로젝트를 준비할 수 있는 기간이 갑자기 늘어났다! 그래서 그런지 늘어졌고 조금 어렵고 복잡한 부분을 맡게되었는데 어떻게 시작할지 감이 안잡힌다. 하지만 내일부터 다시 팀원들과 모르는 부분은 공유해가면서 새로운 기능을 추가적으로 구현해 볼 예정이다.


Entity를 Dto로 반환하여 페이징

람다식 활용하여 Dto 반환

1.
 Page<EventListResponseDto> eventListResponseDtos = events.map(e ->
                EventListResponseDto.builder()
                        .eventNumber(e.getId())
                        .eventName(e.getEventName())
                        .eventCategory(e.getEventCategory())
                        .eventStartDate(e.getEventStartDate())
                        .eventEndDate(e.getEventEndDate())
                        .eventImageUrls(e.getEventImages().stream()
                                .map(i -> i.getEventImageUrl())
                                .collect(Collectors.toList()))
                        .build());

2. 
Page<EventListResponseDto> reponseDto = events.map(events
										-> new EventListResponseDto(events));

3.
Page<EventListResponseDto> reponseDto = events.map(EventListResponseDto :: new):

과정

Entity 반환 페이징

  • 이벤트 목록을 페이징하기 위해 Spring Data JPA 에서 제공하는 Pageable을 사용하여 Entity인Page를 반환받음
# Entity 반환
// 전체 이벤트 목록
    public Page<Event> getEventList(String keyword, EventCategory eventCategory, Pageable pageable) {
        int page = (pageable.getPageNumber() == 0) ? 0 : (pageable.getPageNumber() -1);
        pageable = PageRequest.of(page, 10);

        Page<Event> events = null;
        if (eventCategory == null) {
            events = eventRepository.findByEventNameContaining(keyword, pageable);
        } else {
            events = eventRepository.findByEventNameContainingAndEventCategory(keyword, eventCategory, pageable);
        }
        return events;
    }
    

Dto 반환 페이징

  • eventRepository에서 Page<Event> events를 전달받음
  • 전달받은 events를 람다식 .map을 이용하여 Build한 후 필요한 항목들을Page<EventListResponseDto> 로 반환 받음
# Dto 반환
		// 전체 이벤트 목록
    public Page<EventListResponseDto> toDtoList(String keyword, EventCategory eventCategory, Pageable pageable) {
        int page = (pageable.getPageNumber() == 0) ? 0 : (pageable.getPageNumber() -1);
        pageable = PageRequest.of(page, 10);

        Page<Event> events = null;
        if (eventCategory == null) {
            events = eventRepository.findByEventNameContaining(keyword, pageable);
        } else {
            events = eventRepository.findByEventNameContainingAndEventCategory(keyword, eventCategory, pageable);
        }

        Page<EventListResponseDto> eventListResponseDtos = events.map(e ->
                EventListResponseDto.builder()
                        .eventNumber(e.getId())
                        .eventName(e.getEventName())
                        .eventCategory(e.getEventCategory())
                        .eventStartDate(e.getEventStartDate())
                        .eventEndDate(e.getEventEndDate())
                        .eventImageUrls(e.getEventImages().stream()
                                .map(i -> i.getEventImageUrl())
                                .collect(Collectors.toList()))
                        .build());
        return eventListResponseDtos;
    }
  • eventService의 toDtoList 메소드를 통해 Page<EventListResponseDto>를 반환받음
// 이벤트 리스트 보기
    @GetMapping("/events")
    public String getEventList(@PageableDefault Pageable pageable,
                               @RequestParam(required = false, defaultValue = "", value = "keyword") String keyword,
                               @RequestParam(required = false, value = "eventCategory") EventCategory eventCategory,
                               User loginUser, Model model) {

        if (loginUser != null) {
            model.addAttribute("userNick", loginUser.getUserNickname());
            model.addAttribute("userId", loginUser.getId());
        }

        Page<EventListResponseDto> eventListResponseDtos = eventService.toDtoList(keyword, eventCategory, pageable);
        model.addAttribute("eventListResponseDtos", eventListResponseDtos);

        return "event/eventList";
    }
  

람다식 활용

  • 람다식을 활용 builder 패턴을 쓰지 않고 간단히 Dto 형태로 변환할 수 있음 (all.map(Dto::new)) 형식
# 람다식 사용하여 Dto 반환
		// 전체 이벤트 목록
    public Page<EventListResponseDto> toDtoList(String keyword, EventCategory eventCategory, Pageable pageable) {
        int page = (pageable.getPageNumber() == 0) ? 0 : (pageable.getPageNumber() -1);
        pageable = PageRequest.of(page, 10);

        Page<Event> events = null;
        if (eventCategory == null) {
            events = eventRepository.findByEventNameContaining(keyword, pageable);
        } else {
            events = eventRepository.findByEventNameContainingAndEventCategory(keyword, eventCategory, pageable);
        }

        Page<EventListResponseDto> eventListResponseDtos = events.map(EventListResponseDto::new);
             
        return eventListResponseDtos;
    }
  

html

# html
<nav class="page">
  <ul class="pagination" id="event-pagination"
      th:with="start=${T(java.lang.Math).floor(eventListResponseDtos.number/10)*10 + 1},
        last=(${start + 9 < eventListResponseDtos.totalPages ? start + 9 : eventListResponseDtos.totalPages})">
      <li>
          <a aria-label="First" class="page-number"
             th:href="@{/events(page=1)}">
              <span aria-hidden="true"><<</span>
          </a>
      </li>
      <li th:class="${eventListResponseDtos.first} ? 'disabled'">
          <a aria-label="Previous" class="page-number"
             th:href="${eventListResponseDtos.first} ? '#' : @{/events(page=${eventListResponseDtos.number})}">
              <span aria-hidden="true">&lt;</span>
          </a>
      </li>

      <li th:class="${page == eventListResponseDtos.number + 1} ? 'active'"
          th:each="page: ${#numbers.sequence(start, last)}">
          <a class="page-number" th:href="@{/events(page=${page})}"
             th:text="${page}"></a>
      </li>

      <li th:class="${eventListResponseDtos.last} ? 'disabled'">
          <a aria-label="Next" class="page-number"
             th:href="${eventListResponseDtos.last} ? '#' : @{/events(page=${eventListResponseDtos.number + 2})}">
              <span aria-hidden="true">&gt;</span>
          </a>
      </li>
      <li>
          <a aria-label="Last" class="page-number"
             th:href="@{/events(page=${eventListResponseDtos.totalPages})}">
              <span aria-hidden="true">>></span>
          </a>
      </li>
  </ul>
  <br>
</nav>

주석에 따른 설명


 <!-- 페이징 -->
  <div th:if="${!blogDetails.isEmpty()}">
    <!-- 전역 변수 선언 -->
    <nav
            th:with="
                pageNumber = ${blogDetails.pageable.pageNumber},
                pageSize = ${blogDetails.pageable.pageSize},
                totalPages = ${blogDetails.totalPages},
                startPage = ${T(Math).floor(pageNumber / pageSize) * pageSize + 1},
                tempEndPage = ${startPage + pageSize - 1},
                endPage = (${tempEndPage < totalPages ? tempEndPage : totalPages})"
            aria-label="Page navigation"
    >
      <ul class="pagination ">
        <!-- 처음으로 이동 -->
        <li th:classappend="${pageNumber < pageSize} ? 'disabled'" class="page-item">
          <a class="page-link" th:href="@{/(page=0)}">
            <span>&laquo;</span>
            <span class="sr-only">First</span>
          </a>
        </li>

        <!-- 이전으로 이동 -->
        <li th:classappend="${blogDetails.first} ? 'disabled'" class="page-item">
          <a class="page-link" th:href="${blogDetails.first} ? '#' : @{/(page=${pageNumber - 1})}" aria-label="Previous">
            <span aria-hidden="true">&lt;</span>
            <span class="sr-only">Previous</span>
          </a>
        </li>

        <!-- 특정 페이지로 이동 -->
        <li th:each="page: ${#numbers.sequence(startPage, endPage)}" th:classappend="${page == pageNumber + 1} ? 'active'" class="page-item">
          <a th:text="${page}" class="page-link" th:href="@{/(page=${page - 1})}"></a>
        </li>

        <!-- 다음으로 이동 -->
        <li th:classappend="${blogDetails.last} ? 'disabled'" class="page-item">
          <a class="page-link" th:href="${blogDetails.last} ? '#' : @{/(page=${pageNumber + 1})}" aria-label="Next">
            <span aria-hidden="true">&gt;</span>
            <span class="sr-only">Next</span>
          </a>
        </li>

        <!-- 마지막으로 이동 -->
        <li th:classappend=" ${T(Math).floor(totalPages / pageSize) * pageSize - 1 <= startPage} ? 'disabled'" class="page-item">
          <a class="page-link" th:href="@{/(page=${totalPages - 1})}">
            <span>&raquo;</span>
            <span class="sr-only">Last</span>
          </a>
        </li>
      </ul>
    </nav>
  </div> <!-- /container -->

처음으로 이동

  • 현재 페이지의 인덱스(0부터 시작)가 페이지 사이즈 보다 작을 경우 아직 첫 페이지 라인에 머물로 있는 것이므로, 처음으로 이동 버튼을 비활성화 시킵니다.

이전으로 이동

  • 첫 페이지라면 이전으로 이동 버튼을 비활성화 하고 href 링크도 없는데요. 첫 페이지는 이전이 없기 때문입니다.
  • 그리고 첫 페이지가 아니라면, 현재 페이지 인덱스(0부터 시작) - 1을 하면 이전으로 가겠죠?

특정 페이지로 이동

  • 내가 클릭한 페이지 번호로 이동하는 소스 입니다.
    -startPage 부터 endPage까지 반복문을 돌리는 소스 입니다. 즉, startPage 1 부터 시작하는 것 입니다. 그 값은 page라는 변수에 담겨 사용 됩니다.
  • page와 현재 페이지(인덱스 0 부터) + 1이 같다면 버튼을 찐하게 표시해 줍니다. 현재 페이지를 표시하기 위함이죠.
  • href 링크에 - 1을 한 이유는 page는 1부터 시작하기 때문에 실제 페이지 값은 인덱스 0부터 시작하는 현상 때문 입니다.

다음으로 이동

  • 처음으로 이동과 동일하고, frist 대신 last를 체크, - 1 대신 + 1을 해줍니다.

마지막으로 이동

  • startPage가 앞의 연산 결과 이상 이라면 마지막 페이지 라인에 위치한 상태로 마지막 페이지로 이동할 필요 없이 직접 마지막 페이지 번호를
    누르면 되기 때문에 disable 처리합니다.
  • 그게 아니라면, 전체 페이지 수 - 1만큼 이동합니다. 전체 페이지 수는 index가 0 부터 시작하기 때문에 - 1이 필요 합니다.

페이징
엔티티->DTO 페이징

profile
하루에 한 개념씩

2개의 댓글

comment-user-thumbnail
2022년 7월 15일

소스 코드 보다가 궁금해서 남겨요
controller의 getEventList 메서드에서
eventList는 회원이 아니어도 볼 수 있는 페이지 인가요??
그리고 start, last는 무슨 역할을 하는지 궁금해요~

1개의 답글