멀티캠퍼스 백엔드 과정 47일차[8월 10일] - 게시글 삭제, 수정, 페이징, 부트스트랩

GoldenDusk·2023년 8월 10일
0
post-thumbnail

프로젝트 예제

📌 상세보기 서비스[조회 기능]

작업순서

💫 TodoMapper ===> TodoService ===> TodoController==>JSP

  1. TodoMapper.java 추가
  • 메소드 추가
// 목록 1개씩 상세보기
    TodoVO selectOne(Long tno);
  1. TodoMapper.xml
  • selectOne에 대한 구현 내용 적기
<select id="selectOne" resultType="com.multicampus.springex.domain.TodoVO">
        select * from tb1_todo where tno =#{tno}
    </select>
  1. TodoMapperTests.java
  • 잘 불러오는지 테스트
@Test
    public void testSelectOne(){
        TodoVO todoVO = todoMapper.selectOne(3L);
        log.info(todoVO);
    }
  1. TodoService.java
  • 상세보기를 위한 코드 추가
  • 서비스니까 상세보기 서비스만 추가
  • 리턴타입 getOne(파라미터) 메소드 추가
TodoDTO getOne(Long tno);
  1. TodoServiceImpl.java
  • TodoService의 실제 동작, 구현
  • getOne(파라미터) 재정의 (TodoVO ==> TodoDTO 변환) 리턴 TodoDTO
@Override
    public TodoDTO getOne(Long tno) {
        // TodoService의 실제 동작
        //todoVO ==> todoDTO 변환
        TodoVO todoVO = todoMapper.selectOne(tno);
        TodoDTO todoDTO = modelMapper.map(todoVO, TodoDTO.class);
        return todoDTO;
    }
  1. TodoController.java
  • "/read" 경로로 GET 요청이 들어올 때 해당 식별 번호(tno)에 해당하는 할 일 데이터를 조회하고, 그 데이터를 뷰로 전달하는 역할
// HTTP GET 요청을 처리하며, "/read" 경로로 요청이 들어올 때 호출
    @GetMapping("/read")
    // model은 뷰와 컨트롤러 간의 데이터 교환을 위한 객체
    public void read(Long tno, Model model){
        // todoService라는 서비스 객체를 통해 tno에 해당하는 할 일 데이터를 조회하는 역할
        TodoDTO todoDTO = todoService.getOne(tno);
        log.info(todoDTO);
        // 조회한 할 일 데이터를 model에 "dto"라는 이름으로 추가합니다. 이렇게 모델에 데이터를 추가하면 해당 데이터는 뷰로 전달되어 뷰에서 사용가능
        model.addAttribute("dto", todoDTO);
    }
  1. read.jsp 만들기
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

    <title>Hello, world!</title>
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <h1>Header</h1>
        <div class="row">
            <div class="col">
                <nav class="navbar navbar-expand-lg navbar-light bg-light">
                    <div class="container-fluid">
                        <a class="navbar-brand" href="#">Navbar</a>
                        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
                            <span class="navbar-toggler-icon"></span>
                        </button>
                        <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
                            <div class="navbar-nav">
                                <a class="nav-link active" aria-current="page" href="https://www.daum.net">Daum</a>
                                <a class="nav-link" href="https://www.naver.com">Naver</a>
                                <a class="nav-link" href="#">Pricing</a>
                                <a class="nav-link disabled">Disabled</a>
                            </div>
                        </div>
                    </div>
                </nav>
            </div>
        </div>
    </div>
    <div class="row content">
        <h1>Content</h1>
        <div class="row content">
            <div class="col">
                <div class="card">
                    <div class="card-header">
                        Featured
                    </div>
                    <div class="card-body">
                        <div class="input-group mb-3">
                            <span class="input-group-text">TNO</span>
                            <input type="text" name="tno" class="form-control"
                                   value=<c:out value="${dto.tno}"></c:out> readonly>
                        </div>
                        <div class="input-group mb-3">
                            <span class="input-group-text">Title</span>
                            <input type="text" name="title" class="form-control"
                                   value='<c:out value="${dto.title}"></c:out>' readonly>
                        </div>

                        <div class="input-group mb-3">
                            <span class="input-group-text">DueDate</span>
                            <input type="date" name="dueDate" class="form-control"
                                   value=<c:out value="${dto.dueDate}"></c:out> readonly>

                        </div>

                        <div class="input-group mb-3">
                            <span class="input-group-text">Writer</span>
                            <input type="text" name="writer" class="form-control"
                                   value=<c:out value="${dto.writer}"></c:out> readonly>

                        </div>

                        <div class="form-check">
                            <label class="form-check-label" >
                                Finished &nbsp;
                            </label>
                            <input class="form-check-input" type="checkbox" name="finished" ${dto.finished?"checked":""} disabled >
                        </div>

                        <div class="my-4">
                            <div class="float-end">
                                <button type="button" class="btn btn-primary">Modify</button>
                                <button type="button" class="btn btn-secondary">List</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="row footer">
        <div class="row   fixed-bottom" style="z-index: -100">
            <footer class="py-1 my-1 ">
                <p class="text-center text-muted">geumjuLee</p>
            </footer>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>

</body>
</html>
  1. read.jsp 추가
  • script 추가해서 이벤트 처리
<script>
/*수정에 관련된 기능인데 tno을 담아서 보냄*/
/*위에 버튼 클래스 이름에다가 listener을 달아서 이벤트 처리*/
	document.querySelector(".btn-primary").addEventListener("click",function (e){
	self.location = "/todo/modify?tno="+${dto.tno} // /todo/modify?tno=3이랑 같은 의미
  },false)
   document.querySelector(".btn-secondary").addEventListener("click", function(e){
   self.location = "/todo/list";
                            },false)
 </script>
  1. list.jsp 수정
  • 링크 달아주기
<a href="/todo/read?tno=${dto.tno}" class="text-decoration-none" data-tno="${dto.tno}" >
<c:out value="${dto.title}"/></a>

📌 삭제 기능

  • 수정/삭제 GET 방식으로 조회한 후 POST 방식으로 처리
  1. TodoController 수정
  • read() 수정 /todo/modify?tno=xx 수정
  • @GetMapping({"/read", "/modify"})에서 배열형태로 /modify를 추가해
@GetMapping({"/read", "/modify"})
    // model은 뷰와 컨트롤러 간의 데이터 교환을 위한 객체
    public void read(Long tno, Model model){
        // todoService라는 서비스 객체를 통해 tno에 해당하는 할 일 데이터를 조회하는 역할
        TodoDTO todoDTO = todoService.getOne(tno);
        log.info(todoDTO);
        // 조회한 할 일 데이터를 model에 "dto"라는 이름으로 추가합니다. 이렇게 모델에 데이터를 추가하면 해당 데이터는 뷰로 전달되어 뷰에서 사용가능
        model.addAttribute("dto", todoDTO);
    }
  1. modify.jsp 만들기
  • 이름 위에 getMapping 안에 이름과 꼭 맞춰 줄 것
  • mapper 아직이라 버튼이 동작하지는 않음
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

    <title>Hello, world!</title>
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <h1>Header</h1>
        <div class="row">
            <div class="col">
                <nav class="navbar navbar-expand-lg navbar-light bg-light">
                    <div class="container-fluid">
                        <a class="navbar-brand" href="#">Navbar</a>
                        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
                            <span class="navbar-toggler-icon"></span>
                        </button>
                        <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
                            <div class="navbar-nav">
                                <a class="nav-link active" aria-current="page" href="https://www.daum.net">Daum</a>
                                <a class="nav-link" href="https://www.naver.com">Naver</a>
                                <a class="nav-link" href="#">Pricing</a>
                                <a class="nav-link disabled">Disabled</a>
                            </div>
                        </div>
                    </div>
                </nav>
            </div>
        </div>
    </div>
    <div class="row content">
        <h1>Content</h1>
        <div class="row content">
            <div class="col">
                <div class="card">
                    <div class="card-header">
                        Featured
                    </div>
                    <%--read 부분에서 카피해서 card-body부분만 수정--%>
                    <div class="card-body">
                        <%--get방식 아니라 post 방식으로 처리하기 위해 form태그 넣어주기--%>
                        <form action="/todo/modify" method="post">
                        <div class="input-group mb-3">
                            <span class="input-group-text">TNO</span>
                            <input type="text" name="tno" class="form-control"
                                   value=<c:out value="${dto.tno}"></c:out> readonly>
                        </div>
                        <%--수정할 수 있는 것은 readonly 뺄 것 --%>
                        <div class="input-group mb-3">
                            <span class="input-group-text">Title</span>
                            <input type="text" name="title" class="form-control"
                                   value='<c:out value="${dto.title}"></c:out>'>
                        </div>

                        <div class="input-group mb-3">
                            <span class="input-group-text">DueDate</span>
                            <input type="date" name="dueDate" class="form-control"
                                   value=<c:out value="${dto.dueDate}"></c:out>>

                        </div>

                        <div class="input-group mb-3">
                            <span class="input-group-text">Writer</span>
                            <input type="text" name="writer" class="form-control"
                                   value=<c:out value="${dto.writer}"></c:out> readonly>

                        </div>

                        <div class="form-check">
                            <label class="form-check-label" >
                                Finished &nbsp;
                            </label>
                            <input class="form-check-input" type="checkbox" name="finished" ${dto.finished?"checked":""} >
                        </div>

                        <div class="my-4">
                            <div class="float-end">
                                <button type="button" class="btn btn-danger">Remove</button>
                                <button type="button" class="btn btn-primary">Modify</button>
                                <button type="button" class="btn btn-secondary">List</button>
                            </div>
                        </div>
                        </form>
                        <script>
                            /*수정에 관련된 기능인데 tno을 담아서 보냄*/
                            /*위에 버튼 클래스 이름에다가 listener을 달아서 이벤트 처리*/
                            document.querySelector(".btn-primary").addEventListener("click",function (e){
                                self.location = "/todo/modify?tno="+${dto.tno} // /todo/modify?tno=3이랑 같은 의미
                            },false)
                            document.querySelector(".btn-secondary").addEventListener("click", function(e){
                            self.location = "/todo/list";
                            },false)
                        </script>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="row footer">
        <div class="row   fixed-bottom" style="z-index: -100">
            <footer class="py-1 my-1 ">
                <p class="text-center text-muted">geumjuLee</p>
            </footer>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>

</body>
</html>
  1. modify.jsp에 script 삭제 버튼 처리 추가
<script>
/*Remove 버튼 처리 : form 태그 안에 action을 조정하는 방식으로 처리*/
/*객체 선택*/
const formObj = document.querySelector(".btn-danger").addEventListener("click", function (e) {
	e.preventDefault() /*기본적으로 정의된 이벤트를 작동하지 못하도록 막는 메서드*/
	e.stopPropagation() /*DOM 특징으로 부모와 자식간에 이벤트가 전파현상 버블링과 캡처링이라는 현상을 방지하기 위함*/
	formObj.action ="/todo/remove"
	formObj.method="post"
formObj.submit(), false);
 </script>
  1. TodoController.java
@PostMapping("/remove")
    public String remove(Long tno, RedirectAttributes redirectAttributes){
        log.info("---remove-process-------");
        log.info("tno"+tno);
        todoService.remove(tno);
        return  "redirect:/todo/list";
    }
  1. TodoMapper.java
  • 삭제 메소드 추가
**//delete
    void delete(Long tno);**
  1. TodoMapper.xml
  • delete 동작 적기
<delete id="delete">
        DELETE FROM tb1_todo WHERE tno = #{tno}
    </delete>
  1. TodoService.java
  • 서비스 메소드 추가
void remove(Long tno);
  1. TodoServiceImpl.java
  • 실제 구현 적기
@Override
    public void remove(Long tno) {
        todoMapper.delete(tno);
    }
  • 마지막으로 TodoController로 처리 완료

📌 수정 기능

  1. TodoMapper.java
  • 메서드 추가
void update(TodoVO todoVO);
  1. TodoMapper.xml
  • 자바와 연동
<update id="update">
        update tb1_todo set title=#{title}, dueDate=#{dueDate}, finished=#{finished} where tno=${tno}
    </update>
  1. TodoService.java
  • 수정 메서드 추가
void modify(TodoDTO todoDTO);
  1. TodoServiceImpl
  • 구체화
@Override
    public void modify(TodoDTO todoDTO) {
        TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
        todoMapper.update(todoVO);
    }
  1. Controller가 돌아감

📌 checkbox를 위한 Formatter

  1. formatter > CheckboxFormatter
package com.multicampus.springex.formatter;

import org.springframework.format.Formatter;

import java.text.ParseException;
import java.util.Locale;

public class CheckboxFormatter implements Formatter<Boolean> {
    @Override
    public Boolean parse(String text, Locale locale) throws ParseException {
        if(text==null){
            return false;
        }
        return text.equals("on");
    }

    @Override
    public String print(Boolean object, Locale locale) {
        return object.toString();
    }
}
  1. servlet-context.xml에 빈 등록
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="formatters">
            <!--빈을 넣어줌-->
            <set>
                <bean class="com.multicampus.springex.formatter.LocalDateFormatter"/>
                <bean class="com.multicampus.springex.formatter.CheckboxFormatter"/>
            </set>
        </property>
    </bean>
  1. TodoController 수정
  • post 방식 추가
@PostMapping("/modify")
    public String modify(@Valid TodoDTO todoDTO, BindingResult bindingResult, RedirectAttributes redirectAttributes){
        if(bindingResult.hasErrors()){
            log.info("has..error");
            redirectAttributes.addFlashAttribute("error", bindingResult.getAllErrors());
            redirectAttributes.addAttribute("tno", todoDTO.getTno());
            return "redirect:/todo/modify";
        }
        log.info(todoDTO);

        todoService.modify(todoDTO);

        return "redirect:/todo/list";
    }
  1. modify.jsp 추가
  • 수정
<script>

                            const serverValidResult = {}

                            <c:forEach items="${errors}" var="error">

                            serverValidResult['${error.getField()}'] = '${error.defaultMessage}'

                            </c:forEach>

                            console.log(serverValidResult)
                        </script>
<script>
                        /*Remove 버튼 처리 : form 태그 안에 action을 조정하는 방식으로 처리*/
                        /*객체 선택*/
                        const formObj = document.querySelector("form")
                        document.querySelector(".btn-danger").addEventListener("click", function (e) {
                            e.preventDefault() /*기본적으로 정의된 이벤트를 작동하지 못하도록 막는 메서드*/
                            e.stopPropagation() /*DOM 특징으로 부모와 자식간에 이벤트가 전파현상 버블링과 캡처링이라는 현상을 방지하기 위함*/
                            formObj.action ="/todo/remove"
                            formObj.method="post"
                            formObj.submit()
                        }, false);

                        document.querySelector(".btn-primary").addEventListener("click", function (e) {
                            e.preventDefault()
                            e.stopPropagation()

                            formObj.action="/todo/modify"
                            formObj.method ="post"
                            formObj.submit()
                        }, false);

                        document.querySelector(".btn-secondary").addEventListener("click", function(e){
                            self.location = "/todo/list";
                        },false)
                    </script>
  1. modify의 list 버튼 연동
document.querySelector(".btn-secondary").addEventListener("click", function(e){
                            self.location = "/todo/list";
                        },false)

📌 여기까지 한 부분 캡처

  • 수정

📌 페이징

  • MariaDB/MYSQL에서는 페이징 처리를 위해 select의 마지막 부분에 limit 처리 이용
  • 수 많은 데이터를 한 페이지에서 보여주면, 처리 성능에 영향을 미친다. 또한 브라우저에서도 역시 데이터 양이나 처리 속도에 문제 발생

💫 페이지를 보여주는 작업은 다음과 같은 과정을 통해서 진행

  • 브라우저 주소창에서 페이지 번호를 전달해서 결과를 확인하는 단계
  • jsp에서 페이지 번호를 출력하는 단계
  • 각 페이지 번호에 클릭 이벤트 처리
  • 전체 데이터 개수를 반영해서 페이지 번호 조절

🍉 order by의 문제

  1. 데이터 양이 많을수록 정렬이라는 작업은 많은 리소스와 시간이 소모
  2. 데이터를 이용 시 웹이나 애플리케이션에서 가장 신경 쓰는 부분
    1. 빠르게 처리되는 것
    2. 필요한 양만큼만 데이터를 가져오는 것
  3. 빠르게 동작하는 SQL을 위해서는 먼저 order by를 이용하는 작업을 가능하면 하지 말아야 한다.

🍉 order by보다는 인덱스를 쓰는 것이 더 좋음

  • 인덱스를 통해 정렬을 생략하는 방법
  • 인덱스라는 존재가 이미 정렬된 구조 이기 때문에 별도의 정렬이 필요하지 않음
  • 실행시간에 엄청난 차이가 남

  1. 위의 sql의 경우 이 쿼리에 사용된 힌트(/* + INDEX_DESC(tb1_board pk_board) */)는 옵티마이저에게 인덱스 힌트를 제공하여 특정 인덱스를 사용하도록 유도
  2. 주의해서 봐야 할 점
    1. sort를 하지 않았다는 점
    2. 테이블로 바로 접근이 아니라 pk인 pk_board를 이용해서 접근 한 점
  3. 인덱스 힌트는 성능 최적화를 위해 사용되지만, 주의가 필요
  4. 힌트는 쿼리 옵티마이저에게 명시적으로 어떤 계획을 선택해야 하는지 알려주는 것이기 때문에 옵티마이저가 효율적인 실행 계획을 선택하지 못할 수도 있다. 따라서 힌트를 사용하기 전에 신중한 검토가 필요

🍉 PK_BOARD라는 인덱스

  1. 테이블 생성 시 제약 조건으로 PK를 지정하고 PK의 이름이 pk_board라고 지정
  2. 데이터베이스에서 PK는 중요한 의미를 가지는데 식별자의 의미와 인덱스의 의미를 가진다.
  3. 인덱스 = 색인
    1. 색인은 사람들이 쉽게 찾아볼 수 있게 알파벳 순서나 한글 순서로 정렬
    2. 원하는 내용을 위에서부터 혹은 반대로 찾아나가는데 이를 스캔
  4. 테이블 만들 때 PK를 부여하면 인덱스가 만들어진다.
    1. PK는 식별이라는 의미도 있지만 구조상으로는 인덱스라는 존재(객체)가 만들어지는 것을 의미
  5. 테이블은 마치 책장에 책을 막 넣은 것처럼 중간에 순서가 섞여 있는 경우가 대부분
    1. 인덱스와 실제 데이블을 연결하는 고리ROWID라는 존재
    2. ROWID는 데이터베이스 내의 주소에 해당하는데 모든 데이터는 자신만의 주소를 가지고 있다.

🍉 인덱스와 오라클 힌트(Hint)

  1. 웹 페이지의 목록은 주로 시간의 역순으로 정렬된 결과를 보여준다.
  2. 개발자의 입장에서는 정렬을 안 하는 방식으로 select문을 실행하고 싶어한다.
  3. select문 사용 시 힌트(hint) 사용
    1. 지금 내가 전달한 select문을 이렇게 처리해줬으면 좋겠습니다.
    2. 에러가 나도 sql 실행에 지장을 주지 않기 때문에 원하는대로 나오는 지 꼭 확인하는 습관
    3. 힌트는 개발자가 데이터베이스에 어떤 방식으로 실행해 줘야 하는지 명시하기 때문에 조금 강제성이 부여

🍉 힌트 사용 문법

🍉 FULL 힌트

  • select문을 실행 시 테이블 전체를 스캔할 것이라고 명시
  • FULL 힌트는 테이블의 모든 데이터를 스캔하기 때문에 데이터가 많을 때 상당히 느림

🍉 INDEX_ASC, INDEX_DESC 힌트

  • 가장 많이 사용
  • 인덱스를 순서대로 사용할 것인지 역순으로 이용할 것인지 지정
  • SORT 생략
  • order by 조건이 없어도 bno 순번을 통해 접근하기 때문에 없어도 됨

🍉 페이징 처리할 때 필요한 정보들

💫 페이지가 크게 필요로 하는 것

  • 현재 페이지 번호(page)
  • 이전과 다음으로 이동 가능한 링크의 표시 여부(prev, next)
  • 화면에 보여지는 페이지의 시작 번호와 끝 번호(startPage, endPage)

🍉 실습

  1. 더미데이터 추가
insert into tb1_todo(title, dueDate, writer) (select title, dueDate, writer from tb1_todo);
select * from tb1_todo;
select count(*) from tb1_todo;
  1. 나눠서 확인해보기
  • 2페이지 : 10 skip ⇒ limit 10,10
  • 5페이지 : 40 skip ⇒ limit 40, 10
select * from tb1_todo order by tno desc limit 10; /*최근글목록에서 최근글부터 10개를 가지고 옴*/
select * from tb1_todo order by tno desc limit 10, 10; /*10개를 스킵해서 10개를 가지고 옴*/

select * from tb1_todo order by tno desc; /*500씩 나눠짐*/
  1. PageRequestDTO.java 생성
  • 페이지의 최소단위 최대 단위
  • 한 페이지당 개수를 지정
package com.multicampus.springex.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Positive;

@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageRequestDTO {
    @Builder.Default
    @Min(value=1)
    @Positive
    private int page = 1; //page number 페이지 번호

    @Builder.Default// 글을 10개씩 가지고 옴
    @Min(value = 10)
    @Max(value=100)
    @Positive
    private int size = 10; //1 page per data number 한 페이지당 개수

    public int getSkip(){
        return (page-1)*10;
    }
}
  1. TodoMapper.java
  • 한 페이지씩 처리하는 작업 필요
// 한 페이지씩 처리하는 작업 필요
    List<TodoVO> selectList(PageRequestDTO pageRequestDTO);
  1. TodoMapper.xml
  • SQL 구현
<!--MyBATIS 경우 기본적으로 getXXX, setXXX을 통해 동작하므로, #{skip}, #{size}-->
    <select id="selectList" resultType="com.multicampus.springex.domain.TodoVO">
        select * from tb1_todo order by tno desc limit #{skip}, #{size}
    </select>
  1. TodoMapperTests.java
  • 잘 가지고 오는지 테스트
@Test
    public void testSelectList(){
        PageRequestDTO pageRequestDTO = PageRequestDTO.builder()
                .page(1).size(10).build();
        List<TodoVO> voList = todoMapper.selectList(pageRequestDTO);
        voList.forEach(vo->log.info(vo));
    }
  • 101개 글목록 ⇒ 1페이지당 10개 ⇒ 페이지 번호 11개
  • 전체 데이터의 수 ⇒ count(tno)
  1. TodoMapper.java
  • getCount 추가
int getCount(PageRequestDTO pageRequestDTO);
  1. TodoMapper.xml
  • sql 쿼리 작성
<!--getCount 연결해주기, 전체 개수 반환-->
    <select id="getCount" resultType="int">
        select count(tno) from tb1_todo
    </select>
  1. PageResponseDTO.java
  • 페이징을 담을 DTO
  • 확장 가능
package com.multicampus.springex.dto;

import lombok.Builder;

import java.util.List;

// 어떤 형태를 담을지 지정한해둠 E => 아무거나 담을 수 있는 확장성
public class PageResponseDTO<E> {
    private int page;
    private int size;
    private int total;

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

    // 이전 페이지 존재의 여부
    private boolean prev;
    // 다음 페이지 존재의 여부
    private boolean next;
    // 전체 list 값을 담음
    private List<E> dtoList;

    // 메소드 이름 지정
    // 모든 데이터를 받아와서 초기화 세팅 하겠다.
    @Builder(builderMethodName = "withAll")
    public PageResponseDTO(PageRequestDTO pageRequestDTO, List<E> dtoList, int total){
        this.page = pageRequestDTO.getPage();
        this.size = pageRequestDTO.getSize();
        this.total = total;
        this.dtoList = dtoList;

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

        this.start = this.end - 9;

        int last =  (int)(Math.ceil((total/(double)size)));

        this.end =  end > last ? last: end;

        this.prev = this.start > 1;

        this.next =  total > this.end * this.size;
    }

    // 페이지 번호의 계산
    // 현재 페이지 번호 (page)
    // 화면에 10개씩 페이지 번호를 출력 page 1 => 시작 1, 마지막 10
    // if. page 10인 경우 : 시작 1, 마지막 10
    // if page 11인 경우 : 시작 11, 마지막 20

    // 올림하기 1/10 => ceil(0.1) => 올림 1 * 10 => 10
    // 1. 마지막 페이지 구하기
    /*this.end = (int)(Math.ceil(this.page / 10.0)) * 10;
    2. 시작 페이지
    this.start = this.end - 9;
    3. 전체 개수 123/10.0 => 12.3 => 올림 페이지 개수 13개 만들면 됨
    this.last = int(Math.ceil(total/(double)size))
    */
}
  1. PageRequestDTO.java 수정
package com.multicampus.springex.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Positive;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.LocalDate;
import java.util.Arrays;

@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageRequestDTO {
    @Builder.Default
    @Min(value=1)
    @Positive
    private int page = 1; //page number 페이지 번호

    @Builder.Default// 글을 10개씩 가지고 옴
    @Min(value = 10)
    @Max(value=100)
    @Positive
    private int size = 10; //1 page per data number 한 페이지당 개수

    private String link;

    private String[] types;

    private String keyword;

    private boolean finished;

    private LocalDate from;

    private LocalDate to;

    public int getSkip(){
        return (page-1)*10;
    }
    public String getLink() {
        StringBuilder builder = new StringBuilder();

        builder.append("page=" + this.page);

        builder.append("&size=" + this.size);

        if(finished){
            builder.append("&finished=on");
        }

        if(types != null && types.length > 0){
            for (int i = 0; i < types.length ; i++) {
                builder.append("&types=" + types[i]);
            }
        }

        if(keyword != null){
            try {
                builder.append("&keyword=" + URLEncoder.encode(keyword,"UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }

        if(from != null){
            builder.append("&from=" + from.toString());
        }

        if(to != null){
            builder.append("&to=" + to.toString());
        }

        return builder.toString();
    }

    public boolean checkType(String type){

        if(types == null || types.length == 0){
            return false;
        }
        return Arrays.stream(types).anyMatch(type::equals);
    }

}
  1. TodoService.java
  • 메서드 추가 및 수정
//List<TodoDTO> getAll();
    PageResponseDTO<TodoDTO> getList(PageRequestDTO pageRequestDTO);
  1. TodoServiceImpl.java
  • getAll() 부분 주석처리
  • getList 추가
@Override
    public PageResponseDTO<TodoDTO> getList(PageRequestDTO pageRequestDTO) {

        List<TodoVO> voList = todoMapper.selectList(pageRequestDTO);
        List<TodoDTO> dtoList = voList.stream()
                .map(vo -> modelMapper.map(vo, TodoDTO.class))
                .collect(Collectors.toList());

        int total = todoMapper.getCount(pageRequestDTO);

        PageResponseDTO<TodoDTO> pageResponseDTO = PageResponseDTO.<TodoDTO>withAll()
                .dtoList(dtoList)
                .total(total)
                .pageRequestDTO(pageRequestDTO)
                .build();

        return pageResponseDTO;

    }
  1. TodoMapperTest.java 테스트 코드
@Test
    public void testPaging(){
        PageRequestDTO pageRequestDTO = PageRequestDTO.builder()
                .page(3).size(10).build();

        List<TodoVO> voList = todoMapper.selectList(pageRequestDTO);
        voList.forEach(vo-> log.info(vo));
    }

예습부분

  1. TodoController.java
  • list 부분 수정
@RequestMapping("/list") //localhost:8090/todo/list
    public void list(@Valid PageRequestDTO pageRequestDTO, BindingResult bindingResult, Model model){
        log.info("todo_list");
        // TodoService에서 리턴한 List<TodoDTO> getAll();을 model에다가 담기
        //model.addAttribute("dtoList", todoService.getList());
        //model 'dtoList' 이름으로 목록 데이터가 담겨있다. => list.jsp가 처리해줘야 함
        if(bindingResult.hasErrors()){
            pageRequestDTO = PageRequestDTO.builder().build();
        }
        model.addAttribute("responseDTO", todoService.getList(pageRequestDTO));
    }
  1. list.jsp 수정
<tbody>
  <%--responseDTO의 dtoList를 가져올 것이니--%>
  <c:forEach items="${responseDTO.dtoList}" var="dto">
   <tr>
   <th scope="row"><c:out value="${dto.tno}"/></th>
    <td>
    <a href="/todo/read?tno=${dto.tno}" class="text-decoration-none" data-tno="${dto.tno}" >
    <c:out value="${dto.title}"/></a>
    </td>
    <td><c:out value="${dto.writer}"/></td>
    <td><c:out value="${dto.dueDate}"/></td>
    <td><c:out value="${dto.finished}"/></td>
    </tr>
    </c:forEach>
   </tbody>
  1. list.jsp 수정
</table>
                        <div class="float-end">
                            <ul class="pagination flex-wrap">
                                <c:if test ="${responseDTO.prev}">
                                    <li class="page-item">
                                        <a class="page-link" data-num="${responseDTO.start-1}">Previous</a>
                                    </li>
                                </c:if>

                                <c:forEach begin="${responseDTO.start}" end="${responseDTO.end}" var="num">
                                    <li class="page-item${responseDTO.page == num? "active":""}">
                                        <a class="page-link" data-num="${num}">${num}</a></li>
                                    </li>
                                </c:forEach>

                                <c:if test="${responseDTO.next}">
                                    <li class="page-item">
                                        <a class="page-link" data-num="${responseDTO.end +1}">Next</a>
                                    </li>
                                </c:if>
                            </ul>
                        </div>
                    </div>
                    <script>
                    document.querySelector(".pagination").addEventListener("click", function (e) {
                    e.preventDefault()
                    e.stopPropagation()

                    const target = e.target

                    if(target.tagName !== 'A') {
                    return
                    }
                    const num = target.getAttribute("data-num")

                    self.location =`/todo/list?page=\${num}`
                    },false)
                    </script>

회고

오늘은 복습을 많이 못했다. 다음주 휴강 때 이부분은 보충하자. 그리고 부트 스트랩 이용방법도 알아겠다 다음주에 쉴 때 이용해볼까 고민중

그리고 뭔가 역시 실력은 부족하나 나는 정보를 나누고 나눔받는 게 좋아서 개발이 좋은 것도 있는 것 같다. 물론 이런 협업은 개발자가 아니라도 다른 직업도 있지만 내가 구현해내고 조금 더 나은 방법이 있으면 그것을 다른 사람과 공유하여 학습하고 내가 조금 더 나은 방법이 있으면 제안하고 조율해 나가는 게 좋다.

오늘 강사님이 임시로 조를 짜줬는데 나쁘지 않은 느낌이다. 소통도 잘된다. 그리고 스터디 팀원 분들과도 서로 안되는 코드 내가 공유해주기도 공유받기도 하는 데 뿌듯하면서 짜릿하다. 짜릿해

profile
내 지식을 기록하여, 다른 사람들과 공유하여 함께 발전하는 사람이 되고 싶다. 참고로 워드프레스는 아직 수정중

0개의 댓글