SpringBoot with JPA 프로젝트 5.데이터 페이징처리,새글등록

mingki·2022년 2월 1일
0

SpringBoot & JPA

목록 보기
1/26
post-thumbnail

📚 공부한 책 : 코드로배우는 스프링 부트 웹프로젝트
❤️ github 주소 : https://github.com/qkralswl689/LearnFromCode/tree/main/guestbook2022

1.데이터 목록 페이징 처리

구성요소

  • 시작 페이지 번호
  • 끝 페이지 번호
  • 이전/다음 이동 링크 여부
  • 현재 페이지 번호
  • 페이징구현 코드
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;

// 화면에서 필요한 결과는 PageResultDTO라는 이름으로 생성한다
@Data
            // 다양한 곳에서 사용할 수 있도록 제네릭 타입을 이용해 DTO 와 EN(entity) 이라는 타입을 지정한다
public class PageResultDTO <DTO,EN>{

    // 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;

                                           // Function<EN,DTO> : 엔티티 객체들을 DTO로 변환해주는 기능
    public PageResultDTO(Page<EN> result, Function<EN,DTO> fn){
        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; // 화면에 10페이지씩 보여준다면 시작번호는 무조건 끝번호에서 9를 뺀 값이다

        prev = start > 1;

        end = totalPage > tempEnd ? tempEnd : totalPage;

        next = totalPage > tempEnd;

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

}
  • 테스트 코드
import com.example.guestbook2022.dto.GuestbookDTO;
import com.example.guestbook2022.dto.PageRequestDTO;
import com.example.guestbook2022.dto.PageResultDTO;
import com.example.guestbook2022.entity.Guestbook;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class GuestbookServiceTests {

    @Autowired
    private GuestbookService service;

    @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);
        }*/
        System.out.println(resultDTO);
    }

}
  • 실행쿼리
Hibernate: 
    select
        guestbook0_.gno as gno1_0_,
        guestbook0_.moddate as moddate2_0_,
        guestbook0_.regdate as regdate3_0_,
        guestbook0_.content as content4_0_,
        guestbook0_.title as title5_0_,
        guestbook0_.writer as writer6_0_ 
    from
        guestbook guestbook0_ 
    order by
        guestbook0_.gno desc limit ?
Hibernate: 
    select
        count(guestbook0_.gno) as col_0_0_ 
    from
        guestbook guestbook0_
PageResultDTO(dtoList=[GuestbookDTO(gno=302, title=등록 테스트, content=등록!, writer=userming, regDate=2022-02-01T13:09:49.781778, modDate=2022-02-01T13:09:49.781778),
GuestbookDTO(gno=301, title=Sample Title..., content=Sample Content..., writer=user0, regDate=2022-01-31T19:43:17.140328, modDate=2022-01-31T19:43:17.140328),
GuestbookDTO(gno=300, title=Changed Title..., content=Changed Content..., writer=user0, regDate=2022-01-31T14:16:14.908530, modDate=2022-01-31T14:24:46.650907),
GuestbookDTO(gno=299, title=Title...299, content=Content...299, writer=user9, regDate=2022-01-31T14:16:14.904784, modDate=2022-01-31T14:16:14.904784), 
GuestbookDTO(gno=298, title=Title...298, content=Content...298, writer=user8, regDate=2022-01-31T14:16:14.900567, modDate=2022-01-31T14:16:14.900567), 
GuestbookDTO(gno=297, title=Title...297, content=Content...297, writer=user7, regDate=2022-01-31T14:16:14.892919, modDate=2022-01-31T14:16:14.892919), 
GuestbookDTO(gno=296, title=Title...296, content=Content...296, writer=user6, regDate=2022-01-31T14:16:14.890396, modDate=2022-01-31T14:16:14.890396),
GuestbookDTO(gno=295, title=Title...295, content=Content...295, writer=user5, regDate=2022-01-31T14:16:14.887866, modDate=2022-01-31T14:16:14.887866),
GuestbookDTO(gno=294, title=Title...294, content=Content...294, writer=user4, regDate=2022-01-31T14:16:14.885068, modDate=2022-01-31T14:16:14.885068), 
GuestbookDTO(gno=293, title=Title...293, content=Content...293, writer=user3, regDate=2022-01-31T14:16:14.882828, modDate=2022-01-31T14:16:14.882828)], 
totalPage=31, page=1, size=10, start=1, end=10, prev=false, next=true, pageList=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

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

실제화면에 페이징 기능을 반영한다 -> 컨트롤러 작성 , thymeleaf 작성

☆ 2-1. 컨트롤러 작성

import com.example.guestbook2022.dto.GuestbookDTO;
import com.example.guestbook2022.dto.PageRequestDTO;
import com.example.guestbook2022.service.GuestbookService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;


@Controller
@RequiredArgsConstructor // 자동주입
@RequestMapping("/guestbook")
public class GuestbookController {

    private final GuestbookService service;

    @GetMapping("/")
    public String index(){
        return "redirect:/guestbook/list";
    }

    @GetMapping("/list")
    public void list(@ModelAttribute PageRequestDTO pageRequestDTO , Model model){

        // 실제로 model에 추가되는 데이터 : PageResultDTO
        // => Model을 이용해 GuestbookServiecImpl에서 반환하는 PageResultDTO를 result 라는 이름으로 전달
        model.addAttribute("result", service.getList(pageRequestDTO));
    }

}

☆ 2-2. thymeleaf 작성

  • 부스트트랩의 테이블 구조를 이용하여 전달받은 dtoList 를 이용해 GuestbookDTO들을 출력한다
  • th:each를 이용해 PageResultDTO 안에 있는 dtoList를 반복처리한다
  • 페이지의 이전(previous)와 다음(next)부분은 if를 이용해 처리
  • 현재 페이지 여부를 체크해 'active'라는 이름의 클래스가 출력되도록 작성
  • 이전(previous)의 경우 PageResultDTO의 start 값에서 1적은 값으로 지정
  • 다음(next)의 경우 PageResultDTO의 end 값에서 1큰 값으로 지정
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

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

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

       <h1>gestbook test</h1>

        <table class="table table-striped">
            <thead>

            <tr>
                <th scope="col">Gno</th>
                <th scope="col">Title</th>
                <th scope="col">Writer</th>
                <th scope="col">Regdate</th>

            </tr>
            </thead>
            <tbody>

              <!--th:each를 이용해 PageResultDTO 안에 있는 dtoList를 반복처리한다-->
            <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>

      <!--페이지의 이전(previous)와 다음(next)부분은 if를 이용해 처리-->
           <ul class="pagination h-100 justify-content-center align-items-center">
             <!--현재 페이지 여부를 체크해 'active'라는 이름의 클래스가 출력되도록 작성-->
            <li class="page-item" th:if="${result.prev}">
              <!--이전(previous)의 경우 PageResultDTO의 start 값에서 1적은 값으로 지정-->
                <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>
             <!--현재 페이지 여부를 체크해 'active'라는 이름의 클래스가 출력되도록 작성-->
            <li class="page-item" th:if="${result.next}">
               <!--다음(next)의 경우 PageResultDTO의 end 값에서 1큰 값으로 지정-->
                <a class="page-link" th:href="@{/guestbook/list(page = ${result.end +1})}">Next</a>
            </li>
        </ul>

    </th:block>

</th:block>
  • 출력 화면

3.게시글 등록페이지 구현

게시글 등록페이지 구현

  • 등록 : GET 방식 -> 화면을 보여준다 , POST방식 -> 처리 후 목록 페이지로 이동
  • RedirectAttributes() : 한 번만 화면에서 변수를 사용할 수 있도록 처리
  • addFlashAttribute() : 단 한번만 데이터를 전달하는 용도로 사용한다

☆ 3-1. 컨트롤러 작성

import com.example.guestbook2022.dto.GuestbookDTO;
import com.example.guestbook2022.dto.PageRequestDTO;
import com.example.guestbook2022.service.GuestbookService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@Controller
@RequiredArgsConstructor // 자동주입
@RequestMapping("/guestbook")
public class GuestbookController {

    private final GuestbookService service;

	// 화면을 보여준다
    @GetMapping("/register")
    public void register(){

    }
	// 처리 후 목록 페이지로 이동
    @PostMapping("/register")
    public String registerPost(GuestbookDTO dto, RedirectAttributes redirectAttributes){

        // 새로 추가된 엔티티의 번호
        Long gno = service.register(dto);

        // addFlashAttribute() : 단 한번만 데이터를 전달하는 용도로 사용한다
        // redirectAttributes : 한 번만 화면에서 "msg"라는 이름의 변수를 사용할 수 있도록 처리
        redirectAttributes.addFlashAttribute("msg", gno);

        return "redirect:/guestbook/list";
    }

}

☆ 3-2.register thymeleaf 작성

from 태그에는 action속성값을 "/guestbook/register"로 지정하고 POST방식으로 전송할 수 있도록 한다

<!DOCTYPE html>
<html lang="en" 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 Register Page</h1>
		<!--action속성값을 "/guestbook/register"로 지정하고 POST방식으로 전송할 수 있도록 한다-->
        <form th:action="@{/guestbook/register}" th:method="post">
            <div class="form-group">
                <label >Title</label>
                <input type="text" class="form-control" name="title" placeholder="Enter Title">
            </div>
            <div class="form-group">
                <label >Content</label>
                <textarea class="form-control" rows="5" name="content"></textarea>
            </div>
            <div class="form-group">
                <label >Writer</label>
                <input type="text" class="form-control" name="writer" placeholder="Enter Writer">
            </div>

            <button type="submit" class="btn btn-primary">Submit</button>
        </form>

    </th:block>

</th:block>

☆ 3-3.list thymeleaf에 모달창 설정

게시글 등록 처리 후 처리된 결과를 모달창으로 보여준다

  • JavaScript를 이용해 등록 후 전달되는 msg 값을 확인한다
  • 등록후 msg 변수에 새로 생성된 글의 번호가 할당 -> msg 변수의 값을 이용해 모달창을 실행한다
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

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

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

       <h1>gestbook test</h1>

        <table class="table table-striped">
            <thead>

            <tr>
                <th scope="col">Gno</th>
                <th scope="col">Title</th>
                <th scope="col">Writer</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" 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>

        <div class="modal" tabindex="-1" role="dialog">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">Modal title</h5>
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                            <span aria-hidden="true">&times;</span>
                        </button>
                    </div>
                    <div class="modal-body">
                        <p>Modal body text goes here.</p>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                        <button type="button" class="btn btn-primary">Save changes</button>
                    </div>
                </div>
            </div>
        </div>

        <script th:inline="javascript">
	<!--등록 후 전달되는 msg 값을 확인한다-->
            var msg = [[${msg}]];

            console.log(msg);
          
	<!--등록후 msg 변수에 새로 생성된 글의 번호가 할당 -> msg 변수의 값을 이용해 모달창을 실행한다-->
            if(msg){
                $(".modal").modal();
            }
        </script>

    </th:block>

</th:block>
  • 화면

☆ 3-4.list thymeleaf에 새글작성 페이지 이동버튼 추가

  • 새글작성 페이지 이동버튼 추가
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

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

    <th:block th:fragment="content">
		
      <!-- 그외의 코드 생략 -->
  
            <!--추가-->
           <tr th:each="dto : ${result.dtoList}" >
                <th scope="row">
                    <a th:href="@{/guestbook/read(gno = ${dto.gno}, page= ${result.page})}">
                        [[${dto.gno}]]
                    </a>
                </th>
                <td>[[${dto.title}]]</td>
                <td>[[${dto.writer}]]</td>
                <td>[[${#temporals.format(dto.regDate, 'yyyy/MM/dd')}]]</td>
            </tr>
                
	<!-- 그외의 코드 생략 -->
    </th:block>

</th:block>

☆ 3-5.list thymeleaf에 글 제목클릭시 해당 글 조회페이지로 이동 처리

  • 글 제목클릭시 해당 글 조회페이지로 이동 처리
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

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

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

       <h1>gestbook test
           <span>
             <!--추가-->
                <a th:href="@{/guestbook/register}">
                    <button type="button" class="btn btn-outline-primary">REGISTER
                    </button>
                </a>
            </span>
      </h1>
	<!-- 그외의 코드 생략 -->
    </th:block>

</th:block>
  • 화면
profile
비전공초보개발자

0개의 댓글