9) Todo 조회 기능 개발

  • 목록 화면에서 Title을 누르면 '/todo/read/?tno=xx'와 같이 TodoController를 호출하도록 개발

 

  - TodoMapper 조회 기능 개발

  • TodoMapper에 selectOne()이라는 메서드 추가
// TodoMapper
package org.zerock.springex.mapper;

import org.zerock.springex.domain.TodoVO;
import java.util.List;
public interface TodoMapper {

  String getTime();
  void insert(TodoVO todoVO);
  List<TodoVO> selectAll();

  // 파라미터는 Long 타입으로 tno를 받도록 설계, TodoVO 객체 반환하도록 구성
  TodoVO selectOne(Long tno);
}
  • TodoMapper.xml에 selectOne을 추가
<!-- TodoMapper.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTO Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace = "org.zerock.springex.mapper.TodoMapper">

    ...
    
    <select id = "selectOne" resultType = "org.zerock.springex.domain.TodoVO">
        select * from tbl_todo where tno = #{tno}
    </select>
    
</mapper>
  • 테스트 코드를 통해 현재 데이터베이스에 존재하는 번호로 결과를 확인
// TodoMapperTests
@Test
public void testSelectOne() {
TodoVO todoVO = todoMapper.selectOne(3L);
log.info(todoVO);
}

tno가 3인 목록 조회

 

  - TodoService / TodoServiceImpl의 개발

  • TodoService에 getOne() 메서드 추가
// TodoService
package org.zerock.springex.service;

import ...

public interface TodoService {

  void register(TodoDTO todoDTO);
  List<TodoDTO> getAll();
  
  TodoDTO getOne(Long tno);
}
// TodoServiceImpl
package org.zerock.springex.service;

import ...
public class TodoServiceImpl implements TodoService {

  ...

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

 

  - TodoController의 개발

  • GET 방식으로 동작하는 read() 기능 개발
// TodoController
package org.zerock.springex.controller;

import ...
public class TodoController {

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

 

  • webapp > WEB-INF > views > todo에 read.jsp 추가
  • read.jsp에는 JSTL 관련 설정 추가
<!-- read.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix ="c" uri = "http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
    
    ...
    
</head>
<body>
<div class = "container-fluid">
    
    ...
    
    <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 = "text" 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>
</body>
</html>

 

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

  • Modify 버튼을 누르면 GET 방식의 수정 / 삭제 선택이 가능한 화면으로 이동
<!-- read.jsp -->
<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(e){
  self.location = "/todo/modify?tno="+${dto.tno}
}, false)
document.querySelector(".btn-secondary").addEventListener("click", function(e){
  self.location = "todo/list";
}, false)
</script>

 

  - list.jsp의 링크 처리

  • list.jsp에서는 각 TodoDTO의 title에 'todo/read?tno=xxx'와 같이 이동 가능하도록 링크 처리
<!-- list.jsp -->
<tr>
    <th scope = "row"><c:out value = "${dto.tno}"/></th>
    <td><a href = "/todo/read?tno=${dto.tno}" 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>

링크가 추가된 title 목록

 

 

 10) Todo의 삭제 기능 개발

  • 수정과 삭제는 GET 방식으로 조회한 후 POST 방식으로 처리
  • GET 방식의 내용은 조회 화면과 같지만 스프링 MVC에는 여러 경로를 배열과 같은 표기법을 사용해 하나의 @GetMapping으로 처리 가능
  • read() 기능을 수정해서 수정과 삭제에 같은 메서드 시용
// TodoController
// GetMapping에 "/read"만 적용되어 있던 것을 {}안에 "/modify"와 같이 묶어서 같은 기능을 이용
@GetMapping({"/read", "/modify"})
public void read(Long tno, Model model) {
    TodoDTO todoDTO = todoService.getOne(tno);
    log.info(todoDTO);
    model.addAttribute("dto", todoDTO);
}
  • WEB-INF > views > todo 폴더에 read.jsp를 복사하여 modify.jsp 구성
<!-- modify.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix ="c" uri = "http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
    
    ...
    
</head>
<body>
<div class = "container-fluid">
    <div class = "row">
        <h1>Header</h1>
    </div>
    <div class = "row content">
        <div class = "col">
            <div class="card">
                <div class="card-header">
                    Featured
                </div>
                <div class="card-body">
                	<!-- form 태그 구성, 항목들을 수정 가능하도록 readonly 제거 -->
                    <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>
                    <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 = "text" 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>
                </div>
            </div>
        </div>
    </div>
    
    ...
    
</div>
</body>
</html>
  • Remove 버튼이 생겼으며 수정이 가능해짐

 

  - Remove 버튼의 처리

  • 자바스크립트를 이용해서 <form> 태그의 action을 조정하는 방식으로 동작하게 구성
<!-- modify.jsp -->
	</form>
</div>
<script>
    const formObj = document.querySelector("form")
    document.querySelector(".btn-danger").addEventListener("click", function(e) {
      e.preventDefault()
      e.stopPropagation()
      formObj.action = "/todo/remove"
      formObj.method = "post"
      formObj.submit()
    }, false);
</script>
  • TodoController에는 POST 방식으로 동작하는 remove() 메서드를 설계
// TodoController
@PostMapping("/remove")
public String remove(Long tno, RedirectAttributes redirectAttributes) {
    log.info("----------remove----------");
    log.info("tno: "+tno);
    return "redirect:/todo/list";
}

Remove 버튼을 눌렀을 때 출력되는 로그

 

  - TodoMapper와 TodoService의 처리

  • TodoMapper에 delete() 메서드 추가, TodoMapper.xml에는 SQL을 추가
// TodoMapper
package org.zerock.springex.mapper;

import org.zerock.springex.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.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTO Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace = "org.zerock.springex.mapper.TodoMapper">
    
    ...
    
    <delete id = "delete">
        delete from tbl_todo where tno = #{tno}
    </delete>
</mapper>

 

  • TodoService / TodoServiceImpl에는 remove() 메서드 추가
// TodoService
package org.zerock.springex.service;

import ...

public interface TodoService {

  void register(TodoDTO todoDTO);
  List<TodoDTO> getAll();
  TodoDTO getOne(Long tno);

  void remove(Long tno);
}
// TodoServiceImpl
package org.zerock.springex.service;

import ...

public class TodoServiceImpl implements TodoService {

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

 

  • TodoController에서 TodoService의 remove()를 호출하는 코드 추가
// TodoController
@PostMapping("/remove")
    public String remove(Long tno, RedirectAttributes redirectAttributes) {
    log.info("----------remove----------");
    log.info("tno: "+tno);

    todoService.remove(tno);

    return "redirect:/todo/list";
}

 

  • 결과

 

 11) Todo의 수정 기능 개발

  • TodoMapper에 update() 메서드 추가 및 TodoMapper.xml에 update 기능 추가
// TodoMapper
package org.zerock.springex.mapper;

import ...

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.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTO Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace = "org.zerock.springex.mapper.TodoMapper">
    
    ...
    
    <update id = "update">
        update tbl_todo set title = #{title}, dueDate = #{dueDate}, finished = #{finished} where tno = #{tno}
    </update>

</mapper>

 

  • TodoService와 TodoServiceImpl에서는 TodoDTO를 TodoVO로 변환해서 처리
  • TodoService 인터페이스에 modify() 기능 추가
// TodoService
package org.zerock.springex.service;

import ...

public interface TodoService {

  void register(TodoDTO todoDTO);
  List<TodoDTO> getAll();
  TodoDTO getOne(Long tno);
  void remove(Long tno);
  
  void modify(TodoDTO todoDTO);
}
// TodoServiceImpl
package org.zerock.springex.service;

import ...
public class TodoServiceImpl implements TodoService {

  ...

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

 

  - checkbox를 위한 Formatter

  • 수정 작업에서는 화면에서 체크박스를 이용해서 완료여부(finished) 처리
  • 체크박스가 클릭된 상태일 때 브라우저는 'on'이라는 값을 전송하며, TodoDTO로 데이터를 수집할 때 'on'을 boolean 타입으로 처리할 수 있어야 하므로 Controller에서 데이터를 수집할 때 타입을 변경해주기 위한 CheckboxFormatter를 formatter 패키지에 추가
// org.zerock.springex > controller > formatter > CheckbocFormatter
package org.zerock.springex.controller.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();
  }
}
  • 추가한 CheckbocFormatter는 servlet-context.xml에 등록
<!-- servlet-context.xml -->
<bean id = "conversionService" class = "org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name = "Formatters">
        <set>
            <bean class = "org.zerock.springex.controller.formatter.LocalDateFormatter"/>
            <bean class = "org.zerock.springex.controller.formatter.CheckboxFormatter"/>
        </set>
    </property>
</bean>

 

  - TodoController의 modify()

  • TodoController에서 POST 방식으로 동작하는 modify() 작성
// TodoController
package org.zerock.springex.controller;

import ...

public class TodoController {

  ...
  
  // @Valid를 활용해 피룡 내용 검증, 문제가 있는 경우 다시 '/todo/modify'로 redirect
  // '/todo/modify'로 이동할 때 tno 파라미터가 필요하므로 RedirectAttributes를 이용해 addAttribute()를 이용하고 errors라는 이름으로 BindingResult의 모든 에러들을 전달
  @PostMapping("/modify")
  public String modify(@Valid TodoDTO todoDTO, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
    if(bindingResult.hasErrors()) {
      log.info("has errors.......");
      redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());
      redirectAttributes.addAttribute("tno", todoDTO.getTno());
      return "redirect:/todo/modify";
    }
    
    log.info(todoDTO);
    todoService.modify(todoDTO);
    return "redirect:/todo/list";
  }
}

 

  • WEB-INF > views > todo > modify.jsp에는 검증된 정보를 처리하는 코드 추가
<!-- modify.jsp -->
	</form>
</div>
<!-- <form> 태그가 끝난 후 <script> 태그를 이용해 @Valid 문제 발생 시, 이를 자바스크립트 객체로 필요할 때 사용할 수 있도록 함 -->
<script>
    const serverValidResult = {}
    <c:forEach items = "${errors}" var = "error">
    serverValidResult['${error.getField()}'] = '${error.defaultMessage}'
    </c:forEach>
    console.log(serverValidResult)
</script>
<script>
    const formObj = document.querySelector("form")
    document.querySelector(".btn-danger").addEventListener("click", function(e) {
      e.preventDefault()
      e.stopPropagation()
      formObj.action = "/todo/remove"
      formObj.method = "post"
      formObj.submit()
    }, false);

    // 실제 Modify 버튼의 이벤트 처리에는 <form> 태그 전송
    document.querySelector(".btn-primary").addEventListener("click", function(e) {
      e.preventDefault()
      e.stopPropagation()

      formObj.action = "todo/modify"
      formObj.method = "post"
      formObj.submit()
    }, false);
    
    // List 버튼의 클릭 이벤트 처리
    document.querySelector(".btn-secondary").addEventListener("click", function(e) {
      e.preventDefault()
      e.stopPropagation()

      self.location = "todo/list";
    }, false);
    
</script>

 

+ Recent posts