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을 위해 작성해준다.
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로 검색했을 때 조건을 작성.
<?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>
include refid(reference id):
반복되는 쿼리를 미리 작성해 놓고 재활용 할 수 있게 해준다.
trim
접두어(prefix), 접미어(suffix)를 붙여주거나 지우는 기능. if 태그를 보완하는 역할.
1) prefix 속성 - trim태그 내부 실행될 쿼리문 앞에 설정해둔 속성값을 삽입합니다.
2) suffix 속성 - trim태그 내부 실행될 쿼리문 뒤에 설정해둔 속성값을 삽입합니다.
3) prefixOverrids 속성 - trim태그 내부 실행될 쿼리문 가장 앞의 단어가 속성값에 설쟁해둔 문자와 동일할 경우 문자를 지웁니다.
4) suffixOverrids 속성 - trim 태그 내부 실행될 쿼리문 가장 뒤의 단어가 속성값에 설정해둔 문자와 동일한 경우 문자를 지웁니다.
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);
}
}
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 부분을 다시 만들어 줬다.
<%@ 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>
쿼리를 만들 수 있는 태그들을 지원한다.
if
trim
choose(when, otherwise)
foreach