Dev/Spring

Spring (Todo 기능 개발, TodoService와 TodeServiceImpl 클래스, register 폼 작성, 한글 처리 필터, @Valid 유효성 검증, JSP 검증 메시지 확인, Todo 목록 기능 개발, TodoController의 처리)

Walker_ 2024. 4. 25. 12:40

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 로 접속했을 때, 출력되는 화면 확인

 


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

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

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