Dev/Spring

Spring (Todo 조회 기능 개발, TodoService / TodoServiceImpl의 개발, TodoController의 개발, 수정 / 삭제를 위한 링크 처리, list.jsp의 링크 처리, Todo의 삭제 기능 개발, checkbox 포맷 추가, TodoController의 modify(), 페이징 처리를 위한 TodoMapper, 페이지 처리를 위한 DTO, TodoMapper의 count 처리, TodoService / TodeServ

Walker_ 2024. 4. 26. 17:05

1. Todo 조회 기능 개발

package com.example.spring_project_02.mapper;

import com.example.spring_project_02.domain.TodoVO;

import java.util.List;

public interface TodoMapper {
    String getTime();

    void insert(TodoVO todoVO);

    List<TodoVO> selectAll();

    TodoVO selectOne(Long tno);
}

 

 - TodoMapper.java에 selectOne 추가

<?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.spring_project_02.mapper.TodoMapper">

    <select id="getTime" resultType="string">
        select now()
    </select>

    <insert id="insert">
        INSERT INTO tbl_todo (title, dueDate, writer) VALUES (#{title}, #{dueDate}, #{writer})
    </insert>

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

    <select id="selectOne" resultType="com.example.spring_project_02.domain.TodoVO">
        select * from tbl_todo WHERE tno = #{tno}
    </select>

</mapper>

 

 - TodoMapper.xml에 selectOne 추가

 

@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/root-context.xml")
public class TodoMapperTest {
    @Autowired(required = false)
    private TodoMapper todoMapper;

    @Test
    public void testGetTimer() {
        log.info(todoMapper.getTime());
    }

    @Test
    public void testInsert() {
        TodoVO todoVO = TodoVO.builder()
                .title("스프링 테스트")
                .dueDate(LocalDate.of(2022, 10, 10))
                .writer("user00")
                .build();
        todoMapper.insert(todoVO);
    }

    @Test
    public void testSelectAll() {
        List<TodoVO> todoVOs = todoMapper.selectAll();
        for(TodoVO todoVO : todoVOs) {
            log.info(todoVO);
        }
    }

    @Test
    public void testSelectOne() {
        TodoVO todoVO = todoMapper.selectOne(2L);
        log.info(todoVO);
    }
}

 

 - TodoMapperTest.java에 testSelectOne 추가

 

 - 테스트 정상 작동 확인

 

2. TodoService / TodoServiceImpl의 개발

import com.example.spring_project_02.dto.TodoDTO;

import java.util.List;

public interface TodoService {
    void register(TodoDTO todoDTO);

    List<TodoDTO> getAll();

    TodoDTO getOne(Long tno);
}

 

 - TodoService.java에 코드 추가

@Service
@Log4j2
@RequiredArgsConstructor // 생성자 객체 주입. private final로 선언된 참조변수에 객체를 저장하는 생성자 작성.
public class TodoServiceImpl implements TodoService {
    private final TodoMapper todoMapper;
    private final ModelMapper modelMapper;

    @Override
    public void register(TodoDTO todoDTO) {
        // 1) todoDTO를 전달 받아 2) todoDTO를 todoVO로 변환 후 3) dao의 insert() 호출
        log.info(todoDTO);
        TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
        log.info(todoVO);
        todoMapper.insert(todoVO);
    }

    @Override
    public List<TodoDTO> getAll() {
        List<TodoVO> voList = todoMapper.selectAll(); // dto에서 데이터베이스에서 들고온 VO리스트를 리턴
        List<TodoDTO> dtoList = new ArrayList<>();
        for (TodoVO todoVO : voList) {
            // 개별 VO를 DTO로 변환.
            TodoDTO todoDTO = modelMapper.map(todoVO, TodoDTO.class);
            dtoList.add(todoDTO); // DTO리스트에 저장.
        }
        return dtoList;
    }

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

 

 - TodoServiceImpl에 위 코드 추가

 

3. TodoController의 개발

@Controller
@RequestMapping("/todo")
@Log4j2
@RequiredArgsConstructor
public class TodoController {
    private final TodoService todoService;

    @RequestMapping("/list")
    public void list(Model model) {
        log.info("todo list...");
        model.addAttribute("dtoList", todoService.getAll());
    }

//    @RequestMapping(value = "/register", method = RequestMethod.GET)
    @GetMapping("/register")
    public void registerGET() {
        log.info("GET todo register...");
    }

    @PostMapping("/register")
    public String registerPOST(@Valid TodoDTO todoDTO, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
        log.info("POST todo register...");

        if (bindingResult.hasErrors()) {
            log.info("has error ...");
            redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());
            return "redirect:/todo/register";
        }
        log.info(todoDTO);

        todoService.register(todoDTO);

        return "redirect:/todo/list";
    }

    @GetMapping("/read")
    public void read (Long tno, Model model) {
        TodoDTO todoDTO = todoService.getOne(tno);
        log.info(todoDTO);

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

 

 - TodoController에 코드 추가

 

<%@ 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">

  <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">
    <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="#">Home</a>
            <a class="nav-link" href="#">Features</a>
            <a class="nav-link" href="#">Pricing</a>
            <a class="nav-link disabled">Disabled</a>
          </div>
        </div>
      </div>
    </nav>
    <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="${dto.tno}" readonly>
          </div>
          <div class="input-group mb-3">
            <span class="input-group-text">Title</span>
            <input type="text" name="title" class="form-control" value="${dto.tno}" readonly>
          </div>
          <div class="input-group mb-3">
            <span class="input-group-text">DueDate</span>
            <input type="date" name="dueDate" class="form-control" value="${dto.dueDate}" readonly>
          </div>
          <div class="input-group mb-3">
            <span class="input-group-text">Writer</span>
            <input type="text" name="writer" class="form-control" value="${dto.writer}" 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 class="row content">
      <h1>Content</h1>
      <div class="row footer">
        <!--                <h1>Footer</h1>-->
        <div class="row fixed-bottom" style="z-index: -100">

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

 

 - view > todo > read.jsp에 파일 추가 후 위 코드 작성

 

 

 - 서버 재실행 후 위 URL로 접속해서 화면 확인

 

4. 수정 / 삭제를 위한 링크 처리

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


<script>
  document.querySelector(".btn-primary").addEventListener("click", function () {
    self.location = "/todo/modify?tno=" + ${dto.tno};
  });

  document.querySelector(".btn-secondary").addEventListener("click", function () {
    self.location = "/todo/list";
  })
</script>

 

 - read.jsp 아래에 script 추가

 

5. list.jsp의 링크 처리

<tbody>
<c:forEach var="dto" items="${dtoList}">
<tr>
  <th scope="row">${dto.tno}</th>
  <td><a href="/todo/read?tno=${dto.tno}" class="text-decoration-none"> ${dto.title}</a></td>
  <td>${dto.writer}</td>
  <td>${dto.dueDate}</td>
  <td>${dto.finished}</td>
</tr>
</c:forEach>
</tbody>

 

 - list.jsp에 위 코드 추가

 

6. Todo의 삭제 기능 개발

    @GetMapping({"/read", "/modify"})
    public void read (Long tno, Model model) {
        // 1) request로 전달 받은 tno를 서비스에 전달해서 2) TodoDTO를 반환받아서 3) View에 전달
        TodoDTO todoDTO = todoService.getOne(tno);
        log.info(todoDTO);

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

 

 - TodoController에 { } 하고 "/modify" 추가

 - read.jsp 복사 해서 modify.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">

  <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">
    <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="#">Home</a>
            <a class="nav-link" href="#">Features</a>
            <a class="nav-link" href="#">Pricing</a>
            <a class="nav-link disabled">Disabled</a>
          </div>
        </div>
      </div>
    </nav>
    <div class="card">
      <div class="card-header">
        Featured
      </div>
      <div class="card-body">

          <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="${dto.tno}" readonly>
          </div>
          <div class="input-group mb-3">
            <span class="input-group-text">Title</span>
            <input type="text" name="title" class="form-control" value="${dto.title}">
          </div>
          <div class="input-group mb-3">
            <span class="input-group-text">DueDate</span>
            <input type="date" name="dueDate" class="form-control" value="${dto.dueDate}">
          </div>
          <div class="input-group mb-3">
            <span class="input-group-text">Writer</span>
            <input type="text" name="writer" class="form-control" value="${dto.writer}" 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>
          document.querySelector(".btn-primary").addEventListener("click", function () {
            self.location = "/todo/modify?tno=" + ${dto.tno};
          });

          document.querySelector(".btn-secondary").addEventListener("click", function () {
            self.location = "/todo/list";
          })
        </script>

      </div>
    </div>
    <div class="row content">
      <h1>Content</h1>
      <div class="row footer">
        <!--                <h1>Footer</h1>-->
        <div class="row fixed-bottom" style="z-index: -100">

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

 

 - modify.jsp에 위 코드 작성

 

 - 서버 재 실행 후 URL 기입 시 위 화면 출력 확인

<script>
  const frmModify = document.querySelector('form');

  document.querySelector('.btn-danger').addEventListener('click', function () {
    frmModify.action = '/todo/remove';
    frmModify.method = 'post';
    frmModify.submit();
  })
</script>

 

  - modify.jsp에 script 위 코드로 수정

 

 

 

 - Run > edit configuration > Update classes and resources로 둘 다 설정 확인

 

@PostMapping("/remove")
public String remove(Long tno, RedirectAttributes redirectAttributes) {
    log.info("------------remove------------");
    log.info("tno: " + tno);
    return "redirect:/todo/list";
}

 

 - TodoController에 위 코드 추가

 

 - 서버 재 가동 후 remove 눌렀을 때, list 페이지로 이동되고, log에 remove 출력되는지 확인

 

package com.example.spring_project_02.mapper;

import com.example.spring_project_02.domain.TodoVO;

import java.util.List;

public interface TodoMapper {
    String getTime();

    void insert(TodoVO todoVO);

    List<TodoVO> selectAll();

    TodoVO selectOne(Long tno);

    void delete(Long tno);
}

 

 - TodoMapper에 delete 코드 추가

 

<?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.spring_project_02.mapper.TodoMapper">

    <select id="getTime" resultType="string">
        select now()
    </select>

    <insert id="insert">
        INSERT INTO tbl_todo (title, dueDate, writer) VALUES (#{title}, #{dueDate}, #{writer})
    </insert>

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

    <select id="selectOne" resultType="com.example.spring_project_02.domain.TodoVO">
        select * from tbl_todo WHERE tno = #{tno}
    </select>

    <delete id="delete">
        DELETE FROM tbl_todo WHERE tno = #{tno}
    </delete>

</mapper>

 

 - TodoMapper.xml에 delete 코드 추가

 

    @Test
    public void testDelete() {
        // 1) tno로 데이터를 변환해서 정상 출력 확인 2) 삭제 3) 다시 tno로 데이터를 변환해서 삭제 확인
        Long tno = 2L;
        TodoVO todoVO = todoMapper.selectOne(tno);
        log.info(todoVO);

        todoMapper.delete(tno);

        todoVO = todoMapper.selectOne(tno);
        log.info(todoVO);
    }
}

 

 - TodoMapperTest에 delete 테스트 추가

 

 

 - 삭제돼서 null 값 확인

public interface TodoService {
    void register(TodoDTO todoDTO);

    List<TodoDTO> getAll();

    TodoDTO getOne(Long tno);

    void remove(Long tno);
}

 

 - TodoService에 remove 추가

 

@Service
@Log4j2
@RequiredArgsConstructor // 생성자 객체 주입. private final로 선언된 참조변수에 객체를 저장하는 생성자 작성.
public class TodoServiceImpl implements TodoService {
    private final TodoMapper todoMapper;
    private final ModelMapper modelMapper;

    @Override
    public void register(TodoDTO todoDTO) {
        // 1) todoDTO를 전달 받아 2) todoDTO를 todoVO로 변환 후 3) dao의 insert() 호출
        log.info(todoDTO);
        TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
        log.info(todoVO);
        todoMapper.insert(todoVO);
    }

    @Override
    public List<TodoDTO> getAll() {
        List<TodoVO> voList = todoMapper.selectAll(); // dto에서 데이터베이스에서 들고온 VO리스트를 리턴
        List<TodoDTO> dtoList = new ArrayList<>();
        for (TodoVO todoVO : voList) {
            // 개별 VO를 DTO로 변환.
            TodoDTO todoDTO = modelMapper.map(todoVO, TodoDTO.class);
            dtoList.add(todoDTO); // DTO리스트에 저장.
        }
        return dtoList;
    }

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

    @Override
    public void remove(Long tno) {
        todoMapper.delete(tno);
    }
}

 

 - TodoService에 remove 추가

@PostMapping("/remove")
public String remove(Long tno, RedirectAttributes redirectAttributes) {
    log.info("remove()....");
    log.info("tno: " + tno);

    todoService.remove(tno);

    return "redirect:/todo/list";
}

 

 - TodoController에 코드 수정

 

 

 

 - modify 페이지에서 Remove 클릭 후 제대로 삭제되는 거 확인

 

public interface TodoMapper {
    String getTime();

    void insert(TodoVO todoVO);

    List<TodoVO> selectAll();

    TodoVO selectOne(Long tno);

    void delete(Long tno);

    void update(TodoVO todoVO);
}

 

 - TodoMapper.java에 update 코드 추가

 

<?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.spring_project_02.mapper.TodoMapper">

    <select id="getTime" resultType="string">
        select now()
    </select>

    <insert id="insert">
        INSERT INTO tbl_todo (title, dueDate, writer) VALUES (#{title}, #{dueDate}, #{writer})
    </insert>

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

    <select id="selectOne" resultType="com.example.spring_project_02.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>

</mapper>

 

 - TodoMapper.xml에 update 코드 추가

 

    @Test
    public void testUpdate() {
        Long tno = 12L;
        log.info(todoMapper.selectOne(tno));
        TodoVO todoVO = TodoVO.builder()
                .tno(tno)
                .title("update test")
                .dueDate(LocalDate.parse("2030-12-12"))
                .finished(true)
                .build();
        todoMapper.update(todoVO);
        log.info(todoMapper.selectOne(tno));
    }

 

 - tsetUpdate 코드 추가

 

 

 - 테스트 실행 후 에러 없는 지 확인

 

public interface TodoService {
    void register(TodoDTO todoDTO);

    List<TodoDTO> getAll();

    TodoDTO getOne(Long tno);

    void remove(Long tno);

    void modify(TodoDTO todoDTO);
}

 

 - TodoService에 modify 코드 추가

@Override
public void modify(TodoDTO todoDTO) {
    log.info("modify()...");
    // vo 객체를 생성하면서 매개변수로 받은 (값이 입력되어 있는) dto와 vo를 맵핑을 해서 vo에 값을 입력.
    TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
    log.info(todoVO);
    todoMapper.update(todoVO);
}

 

 - TodoServiceImpl.java에 위 코드 추가

 

7. checkbox 포맷 추가

 - 체크 박스 포매터 파일 추가

 

<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="formatters">
        <set>
            <bean class="com.example.spring_project_02.controller.formatter.LocalDateFormatter"/>
            <bean class="com.example.spring_project_02.controller.formatter.CheckboxFormatter"/>
        </set>
    </property>
</bean>

 

 - servlet-context.xml에 set 안에 bean 한 줄만 더 추가

 

8. TodoController의 modify()

@PostMapping("/modify")
public String modify(@Valid TodoDTO todoDTO,
                     BindingResult bindingResult,
                     RedirectAttributes redirectAttributes) {
    if (bindingResult.hasErrors()) { // 유효성 검사 결과 에러가 있으면 수정페이지로 되돌아감
        log.info(("has error..."));
        redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());
        redirectAttributes.addAttribute("tno", todoDTO.getTno()); // tno가 쿼리스트링으로 전달
        return "redirect:/todo/modify";
    }
    log.info(todoDTO);
    todoService.modify(todoDTO);
    return "redirect:/todo/list";
}

 

 - TodoMapperTest.java에 위 코드 추가

<script>

  const serverValidResult = {};

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

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

  </c:forEach>

  console.log(serverValidResult);

</script>

<script>
  const frmView = document.querySelector("form");

  document.querySelector('.btn-danger').addEventListener('click', function () {
    frmView.action = '/todo/remove';
    frmView.method = 'post';
    frmView.submit();
  })
  document.querySelector('.btn-primary').addEventListener('click', function () {
    frmView.action = '/todo/modify';
    frmView.method = 'post';
    frmView.submit();
  })
</script>

 

 - modify.jsp에 위 스크립트 코드 추가

 

 

 - 글 insert 후 modify로 수정해서 수정 되는 지 확인

 

9. 페이징 처리를 위한 TodoMapper

 - limit 라는 기능을 이용해서 비교적 쉽게 처리가 구현 가능

 

 - 1) 페이징을 위한 SQL 연습

 

insert into tbl_todo (title, dueDate, writer) (select title, dueDate, writer from tbl_todo);

 

 - 위에 SQL 쿼리로 데이터 복사 

 

 - 2) limit 실습

select * from tbl_todo order by tno desc;

 - tno 내림차순으로 조회

 

select * from tbl_todo order by tno desc limit 0,10;

 

 - 가장 최근 데이터 10개를 가져옴

select * from tbl_todo order by tno desc limit 10, 10;

 

 - limit에 두 개의 값을 전달하는 경우 limit (skip), (fetch)가 됨.

 - 10개를 건너뛰고, 다음 10개를 가져와야 하는 경우 위 처럼 작성

 

 - 3) count()의 필요성

 - 페이징 처리를 하기 위해서는 전체 데이터의 개수가 필요.

 - 전체 데이터의 개수는 페이지 번호를 구성할 때 필요

 - 예를 들어 전체 데이터가 30개이면 3 페이지까지만 출력해야 하는 작업에서 사용.

select count(tno) from  tbl_todo;

 

 - 전체 데이터 개수 확인 쿼리

 

10. 페이지 처리를 위한 DTO

 

 - 파일 생성

package com.example.spring_project_02.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;

    @Builder.Default
    @Min(value = 10)
    @Max(value = 100)
    @Positive
    private int size = 10;

    public int getSkip() {
        return (page - 1) * size;
    }
}

 

- 생성한 파일에 위 코드 작성

 

void update(TodoVO todoVO);

List<TodoVO> selectList(PageRequestDTO pageRequestDTO);

 

- TodoMapper에 위 코드 추가

 

<select id="selectList" resultType="com.example.spring_project_02.domain.TodoVO">
    select * from tbl_todo order by tno desc limit #{skip}, #{size}
</select>

 

 - TodoMapper.xml에 위 코드 추가

 

    @Test
    public void testSelectList() {
        PageRequestDTO pageRequestDTO = PageRequestDTO.builder()
                .page(5)
                .size(20)
                .build();
        List<TodoVO> todoVOList = todoMapper.selectList(pageRequestDTO);
        for(TodoVO todoVO : todoVOList) {
            log.info(todoVO);
        }
        todoVOList.forEach(item -> log.info(item));
    }

 

 - Test 코드 추가

 

 - 테스트 실행 후 이상없는지 확인

 

11. TodoMapper의 count 처리

List<TodoVO> selectList(PageRequestDTO pageRequestDTO);

int getCount(PageRequestDTO requestDTO);

 

 - TodoMapper.java에 int getCount 추가

<select id="getCount" resultType="int">
    SELECT COUNT(*) FROM tbl_todo
</select>

 

 - TodoMapper.xml에 getCount 추가

@Test
public void testGetCount() {
    log.info(todoMapper.getCount(PageRequestDTO.builder().build()));
}

 

 - Test 코드 추가

 

 - 실행해서 출력 로그 확인

 

11. 목록 데이터를 위한 DTO와 서비스 계층

 - 파일 생성

package com.example.spring_project_02.dto;

import java.util.List;

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

 

 - 위 코드 작성

 

public class PageResponseDTO<E> {
    @Builder(builderClassName = "withAll")
    public PageResponseDTO(PageRequestDTO pageRequestDTO, List<E> dtoList, int total) {
        this.page = pageRequestDTO.getPage();
        this.size = pageRequestDTO.getSize();

        this.total = total;
        this.dtoList = dtoList;
    }

 

 - PageResponseDTO에 위 코드 추가

 

 - 1) 페이지 번호 계산

 - 페이지 번호를 계산하려면 우선 현재 페이지의 번호 page가 필요.

 - * 현재 page가 1인 경우 : 시작 페이지 start는 1, 마지막 페이지 end는 10

 

 - 2) 이전 prev / 다음 next의 계산

public class PageResponseDTO<E> {
    @Builder(builderClassName = "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;
    }

 

 - PageResponseDTO에 코드 수정

 

 

12. TodoService / TodeServiceImpl

public interface TodoService {
    void register(TodoDTO todoDTO);

//    List<TodoDTO> getAll();

    PageResponseDTO<TodoDTO> getList(PageRequestDTO pageRequestDTO);

    TodoDTO getOne(Long tno);

    void remove(Long tno);

    void modify(TodoDTO todoDTO);
}

 

 - TodoService 위 코드로 변경

package com.example.spring_project_02.service;

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

import java.util.ArrayList;
import java.util.List;

@Service
@Log4j2
@RequiredArgsConstructor // 생성자 객체 주입. private final로 선언된 참조변수에 객체를 저장하는 생성자 작성.
public class TodoServiceImpl implements TodoService {
    private final TodoMapper todoMapper;
    private final ModelMapper modelMapper;

    @Override
    public void register(TodoDTO todoDTO) {
        // 1) todoDTO를 전달 받아 2) todoDTO를 todoVO로 변환 후 3) dao의 insert() 호출
        log.info(todoDTO);
        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);
            List<TodoDTO> dtoList = new ArrayList<>();
            for(TodoVO todoVO : voList) {
                dtoList.add(modelMapper.map(todoVO, TodoDTO.class));
            }
            int total = todoMapper.getCount(pageRequestDTO);

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

//    @Override
    public List<TodoDTO> getAll() {
        List<TodoVO> voList = todoMapper.selectAll(); // dto에서 데이터베이스에서 들고온 VO리스트를 리턴
        List<TodoDTO> dtoList = new ArrayList<>();
        for (TodoVO todoVO : voList) {
            // 개별 VO를 DTO로 변환.
            TodoDTO todoDTO = modelMapper.map(todoVO, TodoDTO.class);
            dtoList.add(todoDTO); // DTO리스트에 저장.
        }
        return dtoList;
    }

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

    @Override
    public void remove(Long tno) {
        todoMapper.delete(tno);
    }

    @Override
    public void modify(TodoDTO todoDTO) {
        log.info("modify()...");
        // vo 객체를 생성하면서 매개변수로 받은 (값이 입력되어 있는) dto와 vo를 맵핑을 해서 vo에 값을 입력.
        TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
        log.info(todoVO);
        todoMapper.update(todoVO);
    }


}

 

 - TodoServiceImpl.java에 위 코드 생성

@GetMapping("/list")
public void list(@Valid PageRequestDTO pageRequestDTO, BindingResult bindingResult, Model model) {
    log.info(pageRequestDTO);

    if(bindingResult.hasErrors()) {
        pageRequestDTO = PageRequestDTO.builder().build();
    }
    model.addAttribute("dtoList", todoService.getList(pageRequestDTO));
}

 

 - TodoController.java에 list 메서드 위 코드로 수정

<tbody>
<c:forEach var="dto" items="${responseDTO.dtoList}">
<tr>
  <th scope="row">${dto.tno}</th>
  <td><a href="/todo/read?tno=${dto.tno}" class="text-decoration-none"> ${dto.title}</a></td>
  <td>${dto.writer}</td>
  <td>${dto.dueDate}</td>
  <td>${dto.finished}</td>
</tr>
</c:forEach>
</tbody>

 

 - list.jsp에 위 코드 부분 수정

 

 

 - 서버 재 실행 후 URL 기입해서 10개만 출력되는 지 확인

 

 

 - page URL을 접속했을 때, Tno 번호 줄어드는 것을 확인

 

https://getbootstrap.com/docs/5.1/components/pagination/

 

Pagination

Documentation and examples for showing pagination to indicate a series of related content exists across multiple pages.

getbootstrap.com

 

 - 접속해서 페이징 번호 코드 복

  </table>
  
  
  <div class="float-end">
    <ul class="pagination flex-wrap">
      <c:if test="${responseDTO.prev}">
        <li class="page-item">
          <a class="page-link">Previous</a>
        </li>
      </c:if>
      <c:forEach var="num" begin="${responseDTO.start}" end="${responseDTO.end}">
        <li class="page-item"><a class="page-link" href="#">#{num}</a></li>
      </c:forEach>
      <c:if test="${responseDTO.next}">
        <li class="page-item">
          <a class="page-link">Next</a>
        </li>
      </c:if>
    </ul>
  </div>
</div>

 

 - list.jsp </table> 끝나는 부분에 위 코드 추가

 

 - 페이지가 10 넘어가면 Previous 나오는 거 확인

 

 

 - 마지막 페이지 갔을 때, Next 버튼 없는 거 확인

 

 


공부 과정을 정리한 것이라 내용이 부족할 수 있습니다.

부족한 내용은 추가 자료들로 보충해주시면 좋을 것 같습니다.

읽어주셔서 감사합니다 :)