240111 todoList 만들기3

MINJU KIM·2024년 1월 11일

Spring

목록 보기
6/13

pagination 만들기

PageResponseDTO.java

package com.example.springex.dto;

import lombok.Builder;
import lombok.Getter;
import lombok.ToString;

import java.util.List;

@Getter
@ToString
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;

    private List<E> dtoList;

    //builder Builder() 이것을 다른 이름으로 네이밍할 때 사용함
    @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;

    }

}

pagination을 위해 작성해준다.

PageRequestDTO.java

package com.example.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; //  디폴트 값 1이다.



    @Builder.Default
    @Min(value = 10)
    @Max(value = 100) // 한페이지당 볼수있는 최대값
    @Positive
    private int size = 10; // 디폴트 값 10이다.

    // 제목 작성자 타입
    private String[] types;
    private String keyword;
    private boolean finished;
    private LocalDate from;
    private LocalDate to;

    private String link;



    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);
    }
}

getLink로 검색했을 때 조건을 작성.

TodoMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springex.mapper.TodoMapper">

    <insert id="insert">
        insert into tbl_todo (title, dueDate, writer)
        values (#{title}, #{dueDate}, #{writer})

    </insert>

    <select id="selectAll" resultType="com.example.springex.domain.TodoVO">
        select * from tbl_todo order by tno desc
    </select>

<!--    selectOne-->
    <select id="selectOne" resultType="com.example.springex.domain.TodoVO">
            select * from tbl_todo where tno = #{tno}
    </select>

    <delete id="delete">
        delete from tbl_todo where tno=#{tno}
    </delete>

    <update id="update">
        update tbl_todo
            set title=#{title}
              , dueDate = #{dueDate}
              , finished = #{finished}
        where tno = #{tno}
    </update>


    <select id="selectList"  resultType="com.example.springex.domain.TodoVO">
            select *
              from tbl_todo
            <include refid="search"></include>
            //밑에 적은 search를 넣어준다. 
             order by tno desc limit #{skip}, #{size}
    </select>
    <sql id="search">
        <where>
            <if test="types != null and types.length > 0">
                <foreach collection="types" item="type" open="(" close=")" separator="OR">
                    <if test="type == 't'.toString()">
                        title like concat('%', #{keyword}, '%')
                    </if>
                    <if test="type == 'w'.toString()">
                        writer like concat('%', #{keyword}, '%')
                    </if>
                </foreach>
            </if>
            <if test="finished">
                <trim prefix="and">
                    finished = 1
                </trim>
            </if>
            <if test="from != null and to != null">
                <trim prefix="and">
                    dueDate between #{from} and #{to}
                </trim>

            </if>


        </where>
    </sql>

    <select id="getCount"  resultType="int">
        select count(tno) from tbl_todo
        <include refid="search"></include>
    </select>

</mapper>

myBatis

  • include refid(reference id):
    반복되는 쿼리를 미리 작성해 놓고 재활용 할 수 있게 해준다.

  • trim
    접두어(prefix), 접미어(suffix)를 붙여주거나 지우는 기능. if 태그를 보완하는 역할.

    1) prefix 속성 - trim태그 내부 실행될 쿼리문 앞에 설정해둔 속성값을 삽입합니다.
    2) suffix 속성 - trim태그 내부 실행될 쿼리문 뒤에 설정해둔 속성값을 삽입합니다.
    3) prefixOverrids 속성 - trim태그 내부 실행될 쿼리문 가장 앞의 단어가 속성값에 설쟁해둔 문자와 동일할 경우 문자를 지웁니다.
    4) suffixOverrids 속성 - trim 태그 내부 실행될 쿼리문 가장 뒤의 단어가 속성값에 설정해둔 문자와 동일한 경우 문자를 지웁니다.

sql

  • concat(): 두 개 이상의 배열을 병합하는 데 사용

TodoServiceImpl.java

package com.example.springex.service;

import com.example.springex.domain.TodoVO;
import com.example.springex.dto.PageRequestDTO;
import com.example.springex.dto.PageResponseDTO;
import com.example.springex.dto.TodoDTO;
import com.example.springex.mapper.TodoMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;

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

@Service
@Log4j2
@RequiredArgsConstructor
public class TodoServiceImpl implements TodoService{
    private final TodoMapper todoMapper;

    private final ModelMapper modelMapper;
    @Override
    public void register(TodoDTO todoDTO) {
        log.info(modelMapper);

        TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);

        log.info(todoVO);

        todoMapper.insert(todoVO);

    }

    @Override
    public PageResponseDTO<TodoDTO> getList(PageRequestDTO pageRequestDTO) {
        List<TodoVO> voList = todoMapper.selectList(pageRequestDTO);
        log.info("getList service : "+voList);
        List<TodoDTO> dtoList = voList.stream()
                .map(vo -> modelMapper.map(vo, TodoDTO.class))
                .collect(Collectors.toList());

        int total = todoMapper.getCount(pageRequestDTO);

        log.info("getList service  전체게시글 수 : "+total);
        PageResponseDTO<TodoDTO> pageResponseDTO = PageResponseDTO.<TodoDTO>withAll()
                                                                .dtoList(dtoList)
                                                                .total(total)
                                                                .pageRequestDTO(pageRequestDTO)
                                                                .build();

        return pageResponseDTO;
    }

    //getAll쓰다가 페이지네이션 추가로 getList로 수정.
//    @Override
//    public List<TodoDTO> getAll() {
//       List<TodoDTO> dtoList = todoMapper.selectAll().stream()
//               .map(vo -> modelMapper.map(vo, TodoDTO.class))
//               .collect(Collectors.toList());
//       return dtoList;
//
//    }

    @Override
    public TodoDTO getOne(Long tno) {
        TodoVO todoVO = todoMapper.selectOne(tno);
        TodoDTO todoDTO = modelMapper.map(todoVO, TodoDTO.class);

        return todoDTO;
//        todoservicejava에서 TodoDTO getOne(Long tno);로 메소드 만들면 구현자 만들라고 뜬다. 구현자 알트엔터 누르면 구현자 만들수잇음
    }

    @Override
    public void remove(Long tno) {

        todoMapper.delete(tno);
    }

    @Override
    public void modify(TodoDTO todoDTO) {
        TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
        todoMapper.update(todoVO);
    }


}

TodoController.java

package com.example.springex.controller;

import com.example.springex.dto.PageRequestDTO;
import com.example.springex.dto.TodoDTO;
import com.example.springex.service.TodoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
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;

import javax.validation.Valid;

@Controller
@RequestMapping("/todo")
@Log4j2
@RequiredArgsConstructor
public class TodoController {

    private final TodoService todoService;

    // list
//    @RequestMapping  ("/list")
//    public void list(Model model) {
//        log.info("to do list");
//        model.addAttribute("dtoList",todoService.getAll());
//    }
    @GetMapping("/list")
    public void list (@Valid PageRequestDTO pageRequestDTO,  BindingResult bindingResult,Model model){
        log.info("TodoController list ::" + pageRequestDTO);

        if(bindingResult.hasErrors()){
            pageRequestDTO = PageRequestDTO.builder().build();
//            에러가 있으면 기본값으로 빌드하게끔 만들어준다.
        }
        model.addAttribute("responseDTO", todoService.getList(pageRequestDTO));
//        괄호 잘 보 자.
    }


                      // register get
    @GetMapping ("/register")
    public void registerGet() {
        log.info("todo registerGet");
    }
    // register post
    @PostMapping("/register")
    public String registerPost(@Valid TodoDTO todoDTO,
                               BindingResult bindingResult,
                               RedirectAttributes redirectAttributes) {
        log.info("todo registerPost");
        if(bindingResult.hasErrors()) {
            log.info("has errors..");
            redirectAttributes.addFlashAttribute("errors",bindingResult.getAllErrors());
            return "redirect:/todo/register";
        }
        todoService.register(todoDTO);

        return "redirect:/todo/list";
    }

    @GetMapping ({"/read", "/modify"})
    public void read(Long tno, PageRequestDTO pageRequestDTO, Model model) {
        TodoDTO todoDTO = todoService.getOne(tno);
        log.info("todo read :: " + todoDTO);

        model.addAttribute("dto", todoDTO);
    }

    @PostMapping("/remove")
    public String remove(Long tno, PageRequestDTO pageRequestDTO, RedirectAttributes redirectAttributes) {
        log.info("=================remove : " + tno + " =================");
        todoService.remove(tno);
        redirectAttributes.addAttribute("page", 1);
        redirectAttributes.addAttribute("size", pageRequestDTO.getSize());

        return "redirect:/todo/list";
    }

    @PostMapping("/modify")
    public String modify(@Valid TodoDTO todoDTO,
                               PageRequestDTO pageRequestDTO,
                               BindingResult bindingResult,
                               RedirectAttributes redirectAttributes) {
        log.info("todo modify");
        if(bindingResult.hasErrors()) {
            log.info("has errors..");
            redirectAttributes.addFlashAttribute("errors",bindingResult.getAllErrors());
            redirectAttributes.addAttribute("tno", todoDTO.getTno());
            return "redirect:/todo/modify";
        }
        todoService.modify(todoDTO);
        redirectAttributes.addAttribute("page", pageRequestDTO.getPage());
        redirectAttributes.addAttribute("size", pageRequestDTO.getSize());

        return "redirect:/todo/list";
    }
}

@RequestMapping ("/list")를 지우고 GetMapping list 부분을 다시 만들어 줬다.

list.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@ 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="row content">
    <div class="col">
        <div class="card">
            <div class="card-body">
                <h5 class="card-title">Search</h5>
                <form action="/todo/list"  id="search" method="get">
                    <input type="hidden" name="size" value="${pageRequestDTO.size}">
                    <div class="mb-3">
                        <input type="checkbox" name="finished"
                                                ${pageRequestDTO.finished?"checked":""}>
                        완료여부
                    </div>
                    <div class="mb-3">
                        <input type="checkbox" name="types" value="t"
                                                ${pageRequestDTO.checkType("t")?"checked":""}>
                        제목
                        <input type="checkbox" name="types" value="w"
                                                ${pageRequestDTO.checkType("w")?"checked":""}>
                        작성자
                        <input type="text" name="keyword" class="form-control"
                        value='<c:out value="${pageRequestDTO.keyword}"/>'
                        >
                    </div>

                    <div class="input-group mb-3 dueDateDiv">
                        <input type="date" name="from" class="form-control" value="${pageRequestDTO.from}">

                        <input type="date" name="to" class="form-control" value="${pageRequestDTO.to}">

                    </div>
                    <div class="mb-3">
                        <div class="float-end">
                            <button class="btn btn-primary" type="submit" >Search</button>
                            <button class="btn btn-info clearBtn" type="reset" id="reset">Clear</button>
                        </div>

                        <div class="card-body">
                            <h5 class="card-title">title</h5>
                            <table class="table">
                                <thead>
                                <tr>
                                    <th scope="col">Tno</th>
                                    <th scope="col">Title</th>
                                    <th scope="col">Writer</th>
                                    <th scope="col">DueDate</th>
                                    <th scope="col">Finished</th>
                                </tr>
                                </thead>
                                <tbody>
                                <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}&${pageRequestDTO.link}"
                                               class="text-decoration-none">
                                                <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>
                            </table>

                            <div class="d-flex justify-content-center">
                                <ul class="pagination flex-wrap">
                                    <c:if test="${responseDTO.prev}">
                                        <li class="page-item">
                                            <a class="page-link" data-num="${responseDTO.start -1}">이전</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>
                                    </c:forEach>

                                    <c:if test="${responseDTO.next}">
                                        <li class="page-item">
                                            <a class="page-link" data-num="${responseDTO.end +1}">다음</a>
                                        </li>
                                    </c:if>
                                </ul>
                            </div>
                        </div>
                    </div>
                </form>
                <%--                등록버튼--%>
                <button type="button" id="register" class="btn btn-primary">register</button>

                        <script>
                            document.querySelector('#register').addEventListener('click', function () {
                                location.href = "/todo/register";
                            })


                            document.querySelector('.pagination').addEventListener('click', function (e) {
                                e.preventDefault();
                                e.stopPropagation();
                                console.log('click');

                                const target = e.target;
                                if (target.tagName !== 'A') {
                                    return
                                }
                                const num = target.getAttribute('data-num');
                                const formObj = document.querySelector('form');
                                formObj.innerHTML += `<input type='hidden' name='page' value='\${num}'>`
                                formObj.submit();
                            }, false)

                            document.querySelector('#reset').addEventListener('click', function (){
                                location.href = '/todo/list';
                            })

                        </script>

</body>
</html>
  • 띄어쓰기 같은거 때문에 +가 붙어서 망했었다.

검색/ 필터링 조건 정의

  • 제목, 작성자 키워드를 이요해서 검색 처리
  • 완료 여부 필터링 처리
  • 특정기간 from to

필요한 데이터

  • keyword
  • finished
  • date(from, to)

MyBatis

쿼리를 만들 수 있는 태그들을 지원한다.
if
trim
choose(when, otherwise)
foreach

0개의 댓글