Spring (Todo 기능 개발, TodoService와 TodeServiceImpl 클래스, register 폼 작성, 한글 처리 필터, @Valid 유효성 검증, JSP 검증 메시지 확인, Todo 목록 기능 개발, TodoController의 처리)
1. Todo 기능 개발
package com.example.spring_project_02.mapper;
import com.example.spring_project_02.domain.TodoVO;
public interface TodoMapper {
String getTime();
void insert(TodoVO todoVO);
}
- TodoMapper.java에 insert 코드 추가
<?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>
</mapper>
- TodoMapper.xml에 insert 코드 추가
package com.example.spring_project_02.mapper;
import com.example.spring_project_02.domain.TodoVO;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.time.LocalDate;
@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);
}
}
- TodoMapperTest에서 testInsert 작성 후 실행
- 출력 로그 확인
- 데이터 베이스에 값 추가 확인
2. TodoService와 TodoServiceImpl 클래스
- service 패키지에 TodoService 인터페이스 파일 추가
package com.example.spring_project_02.service;
import com.example.spring_project_02.dto.TodoDTO;
public interface TodoService {
void register(TodoDTO todoDTO);
}
- 새로 만든 파일에 위 코드 추가
- 같은 패키지에 TodoServiceImpl 자바 클래스 파일 추가
package com.example.spring_project_02.service;
import com.example.spring_project_02.domain.TodoVO;
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;
@Service
@Log4j2
@RequiredArgsConstructor
public class TodoServiceImpl implements TodoService {
private final TodoMapper todoMapper;
private final ModelMapper modelMapper;
@Override
public void register(TodoDTO todoDTO) {
log.info(todoDTO);
TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
log.info(todoVO);
todoMapper.insert(todoVO);
}
}
- TodoServiceImpl 파일에 위 코드 추가
<context:component-scan base-package="com.example.spring_project_02.sample"/>
<context:component-scan base-package="com.example.spring_project_02.config"/>
<context:component-scan base-package="com.example.spring_project_02.service"/>
- root-context.xml에 context: service 추가
- TodoServiceImpl에서 Go To > Test 에서 테스트 파일 생성
package com.example.spring_project_02.service;
import com.example.spring_project_02.dto.TodoDTO;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.time.LocalDate;
import static org.junit.jupiter.api.Assertions.*;
@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/root-context.xml")
class TodoServiceImplTest {
@Autowired
private TodoService todoService;
@Test
public void testRegister() {
TodoDTO todoDTO = TodoDTO.builder()
.title("Test...")
.dueDate(LocalDate.now())
.writer("user1")
.build();
todoService.register(todoDTO);
}
}
- 생성된 테스트 파일에 위 코드 추가
- 테스트 실행 후 출력 로그 확인
- 데이터베이스 값 추가 확인
3. register 폼 작성
<!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 method="post">
<div class="input-group mb-3">
<span class="input-group-text">Title</span>
<input type="text" name="title" class="form-control" placeholder="Title">
</div>
<div class="input-group mb-3">
<span class="input-group-text">DueDate</span>
<input type="date" name="dueDate" class="form-control">
</div>
<div class="input-group mb-3">
<span class="input-group-text">Writer</span>
<input type="text" name="writer" class="form-control" placeholder="Writer">
</div>
<div class="my-4">
<div class="float-end">
<button type="submit" class="btn btn-primary">Submit</button>
<button type="reset" class="btn btn-secondary">Reset</button>
</div>
</div>
</form>
</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 > register.jsp에 위 코드 작성 (test.html)의 코드에서 ㅊㅁbody 부분만 추가
- 프로젝트 실행 후 /todo/register로 접속해서 위 화면 출력 되는 지 확인
- Content 부분은 확인 후 삭제
package com.example.spring_project_02.controller;
import com.example.spring_project_02.dto.TodoDTO;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
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.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/todo")
@Log4j2
public class TodoController {
@RequestMapping("/list")
public void list() {
log.info("todo list...");
}
// @RequestMapping(value = "/register", method = RequestMethod.GET)
@GetMapping("/register")
public void registerGET() {
log.info("GET todo register...");
}
@PostMapping("/register")
public String registerPOST(TodoDTO todoDTO) {
log.info("POST todo register...");
log.info(todoDTO);
return "redirect:/todo/list";
}
}
- TodoController.java에 위 코드로 수정
- 재실행 후 접속해서 값 기입 후 Submit 클릭
- URL : /todo/list 로 넘어가면 정상 작동
- Data 제대로 출력되는 지 확인
- 한글 처리 오류 현상 발생
4. 한글 처리 필터
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<servlet-name>appServlet</servlet-name>
</filter-mapping>
</web-app>
- web.xml 하단에 위 코드 추가
- 한글 출력 테스트
6. @Valid 이용한 서버사이드 유효성 검증
- 1) TodoDTO 검증하기
@ToString
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TodoDTO {
private Long tno;
@NotEmpty
private String title;
@Future
private LocalDate dueDate;
private boolean finished;
@NotEmpty
private String writer;
}
- TodoDTO에 검증 어노테이션 추가
package com.example.spring_project_02.controller;
import com.example.spring_project_02.dto.TodoDTO;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
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.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.validation.Valid;
@Controller
@RequestMapping("/todo")
@Log4j2
public class TodoController {
@RequestMapping("/list")
public void list() {
log.info("todo list...");
}
// @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);
return "redirect:/todo/list";
}
}
- TodoController에 위 코드로 수정
- 등록 페이지에서 작성자 미입력 후 Submit 클릭
- 에러 발생 잡아서 페이지로 다시 오는 지 확인
7. JSP에서 검증 에러 메시지 확인하기
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
- register.jsp 페이지 최상단 위 코드 2줄 추가
</form>
<script>
const serverValidResult = {};
<c:forEach items="${errors}" var="error">
serverValidResult[`${error.getField()}`] = `${error.defaultMessage}`;
</c:forEach>
console.log(serverValidResult);
</script>
- register.jsp </form> 바로 아래에 위 스크립트 추가
- 서버 재실행 후 아무것도 입력하지 않고 Submit을 눌렀을 때, 콘솔에 에러 출력 확인
- 과거 날짜를 입력하면 위 처럼 콘솔에 출력됨
@Controller
@RequestMapping("/todo")
@Log4j2
@RequiredArgsConstructor
public class TodoController {
private final TodoService todoService;
@RequestMapping("/list")
public void list() {
log.info("todo list...");
}
// @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";
}
}
- TodoController에 TodoService 관련 코드 추가
- 서버 재실행 후 DB에 저장되는 지 확인하기 위해 작성 후 Submit
- DB에 데이터 저장 잘 되는 지 확인
8. Todo 목록 기능 개발
- 1) TodoMapper의 개발
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();
}
- TodoMapper에 List 줄 추가
<?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>
</mapper>
- TodoMapper.xml에 select 부분 추가
package com.example.spring_project_02.mapper;
import com.example.spring_project_02.domain.TodoVO;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.time.LocalDate;
import java.util.List;
@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);
}
}
}
- TodoMapperTest.java에 위 코드 추가
- Test 실행 시 제대로 출력되는 지 확인
package com.example.spring_project_02.service;
import com.example.spring_project_02.dto.TodoDTO;
import java.util.List;
public interface TodoService {
void register(TodoDTO todoDTO);
List<TodoDTO> getAll();
}
- 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;
}
}
- TodoServiceImpl.java에 위 코드 추가
@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/root-context.xml")
class TodoServiceImplTest {
@Autowired
private TodoService todoService;
@Test
public void testRegister() {
TodoDTO todoDTO = TodoDTO.builder()
.title("Test...")
.dueDate(LocalDate.now())
.writer("user1")
.build();
todoService.register(todoDTO);
}
@Test
public void testGetAll() {
List<TodoDTO> todoDTOList = todoService.getAll();
for(TodoDTO todoDTO : todoDTOList) {
log.info(todoDTO);
}
}
}
- TodoServiceImplTest.java에 GetAll 테스트 코드 추가
- 테스트 실행 후 제대로 출력되는 지 확인
9. TodoController의 처리
@RequestMapping("/list")
public void list(Model model) {
log.info("todo list...");
model.addAttribute("dtoList", todoService.getAll());
}
- TodoController에 list 부분 수정
- list.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">
<h5 class="card-title">Special title Treatment</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 var="dto" items="${dtoList}">
<tr>
<th scope="row">${dto.tno}</th>
<td>${dto.title}</td>
<td>${dto.writer}</td>
<td>${dto.dueDate}</td>
<td>${dto.finished}</td>
</tr>
</c:forEach>
</tbody>
</table>
</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>
- list.jsp 위 코드로 수정
- /todo/list 로 접속했을 때, 출력되는 화면 확인
공부 과정을 정리한 것이라 내용이 부족할 수 있습니다.
부족한 내용은 추가 자료들로 보충해주시면 좋을 것 같습니다.
읽어주셔서 감사합니다 :)