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

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

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

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

'back-end > Java' 카테고리의 다른 글
[자바 웹 개발 워크북] 4.4 - 스프링 Web MVC 구현하기(5) (0) | 2023.02.22 |
---|---|
[자바 웹 개발 워크북] 4.4 - 스프링 Web MVC 구현하기(4) (0) | 2023.02.21 |
[자바 웹 개발 워크북] 4.4 - 스프링 Web MVC 구현하기(2) (0) | 2023.02.14 |
[자바 웹 개발 워크북] 4.4 - 스프링 Web MVC 구현하기(1) (0) | 2023.02.09 |
[자바 웹 개발 워크북] 4.3 - 스프링 Web MVC 기초 (0) | 2023.01.17 |