JAVAIARY

Spring Boot) 목록처리 본문

Project/2023.02~ ) Study toy 프로젝트

Spring Boot) 목록처리

shiherlis 2023. 2. 20. 18:01
  • 고려사항
    • 화면에서 필요한 목록 데이터에 대한 DTO 생성
    • DTO를 Pageable 타입으로 전환
    • Page<Entity>를 화면에서 사용하기 쉬운 DTO의 리스트 등으로 변환
    • 화면에 필요한 페이지 번호 처리

1. 목록 처리를 위한 DTO

  • PageRequestDTO: 목록 페이지를 요청할 때 사용하는 데이터를 재사용하기 쉽게 만드는 클래스
package com.example.demo.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

@Builder
@AllArgsConstructor
@Data
public class PageRequestDTO {

    private int page;
    private int size;

    public PageRequestDTO(){
        this.page = 1;
        this.size = 10;
    }

    public Pageable getPageable(Sort sort){
        return PageRequest.of(page -1, size, sort);
    }
}

2. 페이지 결과 처리

2-1. PageResultDTO 생성

  • Repository 에서는 페이지 처리 결과를  Page<Entity> 타입으로 반환
  • 따라서 Page<Entity>의 엔티티 객체들을 DTO객체로 변환해서 자료구조로 담아주어야 함
  • 화면출력에 필요한 페이지 정보들을 구성해 주어야 함

PageResultDTO

package com.example.demo.dto;

import lombok.Data;
import org.springframework.data.domain.Page;

import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

@Data
public class PageResultDTO<DTO, EN> {   //DTO와 Entity 타입으로 지정
    
    private List<DTO> dtoList;
    
    public PageResultDTO(Page<EN> result, Function<EN, DTO> fn){
        dtoList = result.stream().map(fn).collect(Collectors.toList());
    }
}
  • Function<EN, DTO> : 엔티티 객체들을 DTO로 변환해 주는 기능

2-2. GuestbookService 인터페이스에 메서드 추가

  • PageRequestDTO를 파라미터로, PageResultDTO를 리턴타입으로 사용하는 getList() 정의
  • 엔티티 객체를 DTO 객체로 변환하는 entityToDTO() 정의
PageResultDTO<GuestbookDTO, Guestbook> getList(PageRequestDTO requestDTO);
 default GuestbookDTO entityToDTO(Guestbook entity){
        
        GuestbookDTO dto = GuestbookDTO.builder()
                .gno(entity.getGno())
                .title(entity.getTitle())
                .content(entity.getContent())
                .writer(entity.getWriter())
                .regDate(entity.getRegDate())
                .modDate(entity.getModDate())
                .build();
        return dto;
    }

2-3. GuestbookServiceImpl에서 getList() 구현

    @Override
    public PageResultDTO<GuestbookDTO, Guestbook> getList(PageRequestDTO requestDTO) {
        Pageable pageable = requestDTO.getPageable(Sort.by("gno").descending());

        Page<Guestbook> result = repository.findAll(pageable);

        Function<Guestbook, GuestbookDTO> fn = (entity -> 
                entityToDTO(entity));
        
        return new PageResultDTO<>(result, fn);
    }
  • entityToDTO()를 이용해서 java.util.Function을 생성하고 이를 PageResultDTO

Tests: 목록 처리 테스트

엔티티 객체가 DTO 객체로 변환되었는지 확인

   @Test
    public void testList(){
        PageRequestDTO pageRequestDTO = PageRequestDTO.builder().page(1).size(10).build();
        PageResultDTO<GuestbookDTO, Guestbook> resultDTO = service.getList(pageRequestDTO);

        for(GuestbookDTO guestbookDTO : resultDTO.getDtoList()){
            System.out.println(guestbookDTO);
        }
    }

콘솔 출력

  • GuestbookDTO 타입으로 출력되는 것 확인

2-4. 목록 데이터 페이지 처리 : PageResultDTO

페이징 처리 구성 요소

start 화면에서 시작 페이지 번호
end 화면에서 끝 페이지 번호
prev, next 이전/다음 이동 링크 여부
page 현재 페이지 번호
package com.example.demo.dto;

import lombok.Data;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Data
public class PageResultDTO<DTO, EN> {   //DTO와 Entity 타입으로 지정

    // DTO 리스트
    private List<DTO> dtoList;

    //총 페이지 번호
    private int totalPage;

    //현재 페이지 번호
    private int page;

    //목록 사이즈
    private int size;

    //시작 페이지 번호, 끝 페이지 번호
    private int start, end;

    // 이전, 다음
    private boolean prev, next;

    //페이지 번호 목록
    private List<Integer> pageList;

    public PageResultDTO(Page<EN> result, Function<EN, DTO> fn){    //function:엔티티 객체들을 DTO로 변환해 주는 기능
        dtoList = result.stream().map(fn).collect(Collectors.toList());

        totalPage = result.getTotalPages();

        makePageList(result.getPageable());
    }

    private void makePageList(Pageable pageable) {
        this.page = pageable.getPageNumber() + 1 ; // 0부터 시작하므로 1을 추가
        this.size = pageable.getPageSize();

        // temp end page
        int tempEnd = (int)(Math.ceil(page/10.0))*10; 

        start = tempEnd -9;

        prev = start > 1;

        end = totalPage > tempEnd ? tempEnd : totalPage; // tempEnd와 totalPage 비교 후 더 작은값 사용

        next = totalPage > tempEnd; 

        pageList = IntStream.rangeClosed(start, end).boxed().collect(Collectors.toList());

    }
}
  • prev, next 는 boolean 값이므로 조건식으로 참, 거짓 판별하여 넣어줌

Tests : GuestbookServiceTests - testList() 수정

 public void testList(){
        PageRequestDTO pageRequestDTO = PageRequestDTO.builder().page(1).size(10).build();
        PageResultDTO<GuestbookDTO, Guestbook> resultDTO = service.getList(pageRequestDTO);

        System.out.println("PREV : " + resultDTO.isPrev());
        System.out.println("NEXT : " + resultDTO.isNext());
        System.out.println("TOTAL : " + resultDTO.getTotalPage());

        System.out.println("-----------------------------------------");
        for (GuestbookDTO guestbookDTO : resultDTO.getDtoList()){
            System.out.println(guestbookDTO);
        }

        System.out.println("=========================================");
        resultDTO.getPageList().forEach(i -> System.out.println(i));
    }

  • 최신순으로 gno 301번부터 10개 출력

3. 컨트롤러와 화면에서의 목록처리

3-1. controller 와 html 작성

GuestbookController

package com.example.demo.controller;


import com.example.demo.dto.PageRequestDTO;
import com.example.demo.entity.Guestbook;
import com.example.demo.service.GuestbookService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/guestbook")
@Log4j2
@RequiredArgsConstructor // 자동주입을 위한 Annotation
public class GuestbookController {

    private final GuestbookService service; // final로 선언
    
    public String index(){
        return "redirect:/guestbook/list";
    }
    
    @GetMapping("/list")
    public void list(PageRequestDTO pageRequestDTO, Model model){
        log.info("----------------" + pageRequestDTO);
        
        model.addAttribute("result", service.getList(pageRequestDTO));
    }
}
  • 자동주입을 위한 @RequiredArgsConstructor 어노테이션 추가
  • GuestbookService  변수 추가
  • list() 추가
  • Spring MVC 는 파라미터를 자동으로 수집해 주는 기능이 있으므로, 화면에서 page와 size라는 파라미터를 전달하면 PageRequestDTO 객체로 자동으로 수집됨
  • Model은 결과 데이터를 화면에 전달하기 위해 사용

list.html 작성

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">

<th:block th:replace="~{/layout/basic :: setContent(~{this::content} )}">

    <th:block th:fragment="content">

        <h1 class="mt-4">GuestBook List Page</h1>

        <table class="table table-striped">
            <thead>
            <tr>
                <th scope="col">No.</th>
                <th scope="col">Gno</th>
                <th scope="col">Title</th>
                <th scope="col">Regdate</th>
            </tr>
            </thead>
            <tbody>

            <tr th:each="dto : ${result.dtoList}">
                <th scope="row">[[${dto.gno}]]</th>
                <td>[[${dto.title}]]</td>
                <td>[[${dto.writer}]]</td>
                <td>[[${#temporals.format(dto.regDate,'yyyy/MM/dd')}]]</td>
            </tr>
            </tbody>
        </table>

        <ul class="pagination h-100 justify-content-center align-items-center">
            <li class="page-item " th:if="${result.prev}">
                <a class="page-link" href="#" tabindex="-1">Previous</a>
            </li>

            <li th:class=" 'page-item ' + ${result.page == page ? 'active' : ''}" th:each="page: ${result.pageList}">
                <a class="page-link" href="#">[[${page}]]</a>
            </li>

            <li class="page-item " th:if="result.next">
                <a class="page-link" href="#">Next</a>
            </li>
        </ul>
    </th:block>

</th:block>

</html>

  • 목록 출력 확인

3-2. 페이지 번호 링크 처리

  • th:href 를 이용하여 작성
<ul class="pagination h-100 justify-content-center align-items-center">
            <li class="page-item " th:if="${result.prev}">
                <a class="page-link" th:href="@{/guestbook/list(page= ${result.start -1})}" tabindex="-1">Previous</a>
            </li>

            <li th:class=" 'page-item ' + ${result.page == page ? 'active' : ''}" th:each="page: ${result.pageList}">
                <a class="page-link" th:href="@{/guestbook/list(page= ${page})}">[[${page}]]</a>
            </li>

            <li class="page-item " th:if="${result.next}">
                <a class="page-link" th:href="@{/guestbook/list(page= ${result.end + 1})}">Next</a>
            </li>
        </ul>