1. ModelMapper 라이브러리

  • TodoService와 TodoDTO에 Lombok을 적용시키는 것이 더 좋기 때문에 다시 구성하기
// TodoDTO
package org.zerock.jdbcex.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDate;

@Builder
// @Data는 getter / setter / toString / equals / hashCode 등을 모두 컴파일할 때 생성
// VO에서 getter만 사용하여 읽기 전용으로 구성하는 것과 차이가 있음
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TodoDTO {

  private Long tno;
  private String title;
  private LocalDate dueDate;
  private Boolean finished;
}
// TodoVO
package org.zerock.jdbcex.domain;

import lombok.*;

import java.time.LocalDate;

@Getter
@Builder
@ToString
// 파라미터가 없는 생성자와 모든 필드값이 필요한 생성자를 만들어냄
@AllArgsConstructor
@NoArgsConstructor
public class TodoVO {

  private Long tno;
  private String title;
  private LocalDate dueDate;
  private boolean finished;
}

 

  • DTO → VO, VO → DTO 변환은 ModelMapper 라이브러리를 이용해 처리
  • ModelMapper 라이브러리는 build.gradle파일에서 추가
  • dependencies에 " implementation group: 'org.modelmapper', name: 'modelmapper', version: '3.0.0' " 추가

 

  • 프로젝트에 util 패키지 추가 > ModelMapper의 설정을 변경하고 쉽게 사용할 수 있는 MapperUtil을 enum으로 생성
package org.zerock.jdbcex.util;

import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;

public enum MapperUtil {
  INSTANCE;

  private ModelMapper modelMapper;

  MapperUtil() {
    this.modelMapper = new ModelMapper();
    // ModelMapper의 설정을 변경하려면 getConfiguration()을 이용해서 private로 선언된 필드도 접근할 수 있도록 설정을 변경
    this.modelMapper.getConfiguration()
        .setFieldMatchingEnabled(true)
        .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
        .setMatchingStrategy(MatchingStrategies.STRICT);
  }

  // get()으로 ModelMapper를 사용할 수 있도록 구성
  public ModelMapper get() {
    return modelMapper;
  }
}

 


 

 

 

[자바 웹 개발 워크북] 2.2 - 프로젝트 내 JDBC 구현

1. Lombok 라이브러리 Lombok을 사용하면 자바에서 클래스 작성 시 getter / setter 생성 또는 생성자 함수 정의하는 작업을 간단한 annotation 추가만으로 끝낼 수 있음 getter / setter: @Getter, @Setter, @Data 등을

data-science-study.tistory.com

실습(이전 글에 이어서)

 7) TodoService와 ModelMapper 테스트

  - DTO와 VO 둘 다 이용해야 하는 TodoService를 구성하고 ModelMapper의 동작 확인

// TodoService
package org.zerock.jdbcex.service;

import org.modelmapper.ModelMapper;
import org.zerock.jdbcex.dao.TodoDAO;
import org.zerock.jdbcex.domain.TodoVO;
import org.zerock.jdbcex.dto.TodoDTO;
import org.zerock.jdbcex.util.MapperUtil;

public enum TodoService {
  INSTANCE;

  private TodoDAO dao;
  private ModelMapper modelMapper;

  TodoService() {
    dao = new TodoDAO();
    modelMapper = MapperUtil.INSTANCE.get();

  }

  // TodoDTO를 파라미터로 받아서 TodoVO로 변환하는 과정을 ModelMapper로 처리
  // print하여 잘 변환됐는지 확인하고
  // TodoDAO를 이용해서 TodoVO를 insert
  public void register(TodoDTO todoDTO) throws Exception {
    TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
    System.out.println("todoVO: " + todoVO);
    dao.insert(todoVO);
    // insert는 int를 반환하므로 이를 이용한 예외처리도 가능
  }
}

 

  - TodoServieTests 에서 테스트용 코드 작성

package org.zerock.service;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.zerock.jdbcex.dto.TodoDTO;
import org.zerock.jdbcex.service.TodoService;

import java.time.LocalDate;

public class TodoServiceTests {

  private TodoService todoService;

  @BeforeEach
  public void ready() {
    todoService = TodoService.INSTANCE;
  }

  @Test
  public void testRegister() throws Exception {
    TodoDTO todoDTO = TodoDTO.builder()
        .title("JDBC Test Title")
        .dueDate(LocalDate.now())
        .build();

    todoService.register(todoDTO);
  }
}

todoVO에 todoDTO로 부터 받은 값이 정상적으로 정의되었음을 확인
tbl_todo 테이블에도 값이 정상적으로 insert되었음을 확인


 

2. Log4j2와 @Log4j2

  • 레벨을 설정하여 개발할 때 필요한 레벨의 로그와 실제 운영 시 필요한 레벨의 로그를 구분
  • 예를 들어, System.out.println은 개발 시에는 확인용으로 많이 사용하지만, 개발이 끝난 후 운영 시에는 필요 x

 

  • Log4j2에는 Appender를 사용하여 어떤 방식으로 기록할 것인지 결정(System.out.println 대신 Consol Appender 사용)
  • Log4j2의 레벨은 중요도의 개념, 로그의 레벨을 지정하면 해당 레벨 이상의 로그들만 출력되어, 개발할 때는 로그의 레벨을 INFO 이하로 개발, 운영할 때는 ERROR나 WARN 이상으로 개발

    • 레벨이 INFO이면 로그에 INFO, WARN, ERROR, FATAL이 출력
    • 레벨이 ERROR이면 로그에 ERROR, FATAL이 출력

 

  • Log4j2 사용을 위해 build.gradle에 라이브러리 설치
// build.gradle의 dependencies에 추가
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.2'
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.17.2'
implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.17.2'

 

 1) log4j2.xml 설정 파일

  • log4j2 라이브러리의 설정은 log4j2.xml 파일 이용, 해당 파일에 레벨이나 Appender 지정
  • 프로젝트의 resources 폴더 > log4j2.xml 생성
// log4j2.xml
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration status = "WARN">
    <Appenders>
        <Console name = "Console" target = "SYSTEM_OUT">
            <PatternLayout pattern = "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36}- %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level = "info">
            <Appender ref = "Console"/>
        </Root>
    </Loggers>
</Configuration>

 

 2) Log4j2 어노테이션

  • 기존 TodoService 코드에 @Log4j2 어노테이션 추가하고, System.out.println을 log.info()로 변경
package org.zerock.jdbcex.util.service;

import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.zerock.jdbcex.dao.TodoDAO;
import org.zerock.jdbcex.domain.TodoVO;
import org.zerock.jdbcex.dto.TodoDTO;
import org.zerock.jdbcex.util.MapperUtil;

@Log4j2
public enum TodoService {
  INSTANCE;

  private TodoDAO dao;
  private ModelMapper modelMapper;

  TodoService() {
    dao = new TodoDAO();
    modelMapper = MapperUtil.INSTANCE.get();

  }

  public void register(TodoDTO todoDTO) throws Exception {
    TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
    // System.out.println("todoVO: " + todoVO);
    log.info(todoVO);
    dao.insert(todoVO);

  }
}

    • TodoServiceTests에서 testRegister()를 실행하면 변경된 로그가 출력됨
    • 또한, HikariCP의 로그도 다르게 출력됨(HikariCP가 내부적으로 slf4j 라이브러리를 이용하고 있고, log4j-slf4j-impl 라이브러리가 Log4j2를 이용할 수 있도록 설정되기 때문)

 

  • 테스트 환경에서 @Log4j2 사용하기
    • 테스트 환경에서 @Log4j2 기능을 활용하기 위해 테스트 환경에서도 어노테이션을 처리하는 testAnnotationprocessor와 testCompileOnly 설정 추가
// build gradle의 dependencies에 추가
testCompileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.24'
testAnnotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.24'
      • TodoServiceTests에 테스트 코드를 @Log4j2를 이용하도록 수정
package org.zerock.service;

import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.zerock.jdbcex.dto.TodoDTO;
import org.zerock.jdbcex.util.service.TodoService;

import java.time.LocalDate;

@Log4j2
public class TodoServiceTests {

  private TodoService todoService;

  @BeforeEach
  public void ready() {
    todoService = TodoService.INSTANCE;
  }

  @Test
  public void testRegister() throws Exception {
    TodoDTO todoDTO = TodoDTO.builder()
        .title("JDBC Test Title")
        .dueDate(LocalDate.now())
        .build();

    // 테스트 코드에서 Log4j2가 사용됨을 확인
    log.info("---------------------------------");
    // TodoService에서 한 번, TodoServiceTests에서 한 번, 총 두 번 출력되도록 입력
    log.info(todoDTO);
    todoService.register(todoDTO);
  }
}

    • "---------------------------------"가 출력되어 TodoServiceTests에서도 log.info()가 동작함을 확인
    • TodoDTO의 내용이 두 번 출력되어 위의 출력은 TodoServiceTests에서, 밑의 출력은 TodoService에서 된 것을 확인

 

 

3. 컨트롤러와 서비스 객체 연동

  • TodoService와 TodoDAO의 연동을 확인한 후, 마지막으로 Servlet으로 작성된 Controller와 TodoService 연동
  • 실습 ↓↓↓↓↓

 


실습

 1) 목록 기능 구현

  - TodoListcontroller

  - Get 방식

package org.zerock.jdbcex.controller;

import lombok.extern.log4j.Log4j2;
import org.zerock.jdbcex.service.TodoService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "todoListController", value = "/todo/list")
@Log4j2
public class TodoListController extends HttpServlet {
  private TodoService todoService = TodoService.INSTANCE;

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    log.info("todo list.................");
  }
}

아직 /todo/list 경로에 화면은 나타나지 않지만 로그에 "todo list..........."가 정상적으로 출력됨을 확인

 

  - TodoService의 목록 기능 구현(개발은 DAO → Service → Controller 순서로 하며, TodoDAO는 개발 완료했으므로 TodoService 에 listAll() 기능 추가)

// TodoService
package org.zerock.jdbcex.service;

import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.zerock.jdbcex.dao.TodoDAO;
import org.zerock.jdbcex.domain.TodoVO;
import org.zerock.jdbcex.dto.TodoDTO;
import org.zerock.jdbcex.util.MapperUtil;

import java.util.List;
import java.util.stream.Collectors;

@Log4j2
public enum TodoService {
  INSTANCE;

  private TodoDAO dao;
  private ModelMapper modelMapper;

  TodoService() {
    dao = new TodoDAO();
    modelMapper = MapperUtil.INSTANCE.get();

  }

  // TodoDTO를 파라미터로 받아서 TodoVO로 변환하는 과정을 ModelMapper로 처리
  // print하여 잘 변환됐는지 확인하고
  // TodoDAO를 이용해서 TodoVO를 insert
  public void register(TodoDTO todoDTO) throws Exception {
    TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
    // System.out.println("todoVO: " + todoVO);
    log.info(todoVO);
    dao.insert(todoVO);
    // insert는 int를 반환하므로 이를 이용한 예외처리도 가능
  }
  
  // listAll() 메서드 작성
  public List<TodoDTO> listAll() throws Exception {
    List<TodoVO> voList = dao.selectAll();
    log.info("voList....................");
    log.info(voList);
    List<TodoDTO> dtoList = voList.stream()
        .map(vo -> modelMapper.map(vo, TodoDTO.class))
        .collect(Collectors.toList());
    
    return dtoList;
  }
}

  - listAll()은 TodoDAO에서 가져온 TodoVO 목록을 모두 TodoDTO로 변환해서 반환

  - 이때, ModelMapper와 Java Stream의 map()을 이용해 간단히 처리

 

  - TodoListController 수정

  - TodoListController에서 HttpServletRequest의 setAttribute()를 이용해서 TodoService 객체가 반환하는 데이터를 저장하고 RequestDipatcher를 이용해서 JSP로 전달

// TodoListController
package org.zerock.jdbcex.controller;

import lombok.extern.log4j.Log4j2;
import org.zerock.jdbcex.dto.TodoDTO;
import org.zerock.jdbcex.service.TodoService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@WebServlet(name = "todoListController", value = "/todo/list")
@Log4j2
public class TodoListController extends HttpServlet {
  private TodoService todoService = TodoService.INSTANCE;

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    log.info("todo list.................");

    try {
      List<TodoDTO> dtoList = todoService.listAll();
      req.setAttribute("dtoList", dtoList);
      req.getRequestDispatcher("/WEB-INF/todo/list.jsp").forward(req, resp);
    } catch (Exception e) {
      log.error(e.getMessage());
      throw new ServletException("list error");
    }
  }
}

 

  - WEB-INF 폴더 > todo 디렉토리 생성 > list.jsp 파일 작성

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri = "http://java.sun.com/jsp/jstl/core" prefix ="c" %>
<html>
<head>
    <title>Todo List</title>
</head>
<body>
<h1>Todo List</h1>
<ul>
    <c:forEach items = "${dtoList}" var = "dto">
        <li>${dto}</li>
    </c:forEach>
</ul>
</body>
</html>

 

  - 결과

  -  tbl_todo 테이블에 있는 데이터들을 jsp에서 출력

 

 

 2) 등록 기능 구현

  - GET 방식으로 등록 화면을 확인

  - <form> 태그 내에 입력 항목 채운 뒤

  - POST 방식으로 처리

  - 처리 후에는 목록화면으로 redirect하는 PRG(Post - Redirect - get) 패턴 방식

 

  - TodoService의 등록 기능 구현(이전에 만들어 둔 register() 메서드가 TodoService의 등록 기능임)

package org.zerock.jdbcex.service;

import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.zerock.jdbcex.dao.TodoDAO;
import org.zerock.jdbcex.domain.TodoVO;
import org.zerock.jdbcex.dto.TodoDTO;
import org.zerock.jdbcex.util.MapperUtil;

import java.util.List;
import java.util.stream.Collectors;

@Log4j2
public enum TodoService {
  INSTANCE;

  private TodoDAO dao;
  private ModelMapper modelMapper;

  TodoService() {
    dao = new TodoDAO();
    modelMapper = MapperUtil.INSTANCE.get();

  }

  // TodoDTO를 파라미터로 받아서 TodoVO로 변환하는 과정을 ModelMapper로 처리
  // print하여 잘 변환됐는지 확인하고
  // TodoDAO를 이용해서 TodoVO를 insert
  public void register(TodoDTO todoDTO) throws Exception {
    TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
    // System.out.println("todoVO: " + todoVO);
    log.info(todoVO);
    dao.insert(todoVO);
    // insert는 int를 반환하므로 이를 이용한 예외처리도 가능
  }

  // listAll() 메서드 작성
  public List<TodoDTO> listAll() throws Exception {
    List<TodoVO> voList = dao.selectAll();
    log.info("voList....................");
    log.info(voList);
    List<TodoDTO> dtoList = voList.stream()
        .map(vo -> modelMapper.map(vo, TodoDTO.class))
        .collect(Collectors.toList());

    return dtoList;
  }
}

 

  - TodoRegisterController 구현

  - controller 패키지의 HttpServlet을 상속받도록 선언

  - GET / POST 모두 사용

// TodoRegisterController
package org.zerock.jdbcex.controller;

import com.sun.tools.javac.comp.Todo;
import lombok.extern.log4j.Log4j2;
import org.zerock.jdbcex.dto.TodoDTO;
import org.zerock.jdbcex.service.TodoService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDate;
import java.util.List;
import java.time.format.DateTimeFormatter;

@WebServlet(name = "todoRegisterController", value = "/todo/register")
@Log4j2
public class TodoRegisterController extends HttpServlet {
  private TodoService todoService = TodoService.INSTANCE;
  private final DateTimeFormatter DATEFORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

  // GET 방식으로 호출되는 경우, "/WEB-INF/todo/register.jsp" 파일에서 입력 화면을 보여줌
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    log.info("/todo/register GET .......");
    req.getRequestDispatcher("/WEB-INF/todo/register.jsp").forward(req, resp);
  }

  // '/todo/register'에서 <form> 태그 내에 title과 dueDate를 POST 방식으로 전송ㄱ
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    TodoDTO todoDTO = TodoDTO.builder()
        .title(req.getParameter("title"))
        .dueDate(LocalDate.parse(req.getParameter("dueDate"), DATEFORMATTER))
        .build();

    log.info("/todo/register POST .......");
    log.info(todoDTO);
    try {
      todoService.register(todoDTO);
    } catch (Exception e) {
      e.printStackTrace();
    }
    resp.sendRedirect("/todo/list");
  }

}

'/todo/register' 경로로 이동했을 때 GET으로 호출된 입력 화면
입력 후 'REGISTER' 클릭 시, 목록 화면으로 Redirect 되어 넘어가고, 목록에는 새로 입력한 데이터가 추가되어 있음

 

 

 3) 조회 기능 구현

  - GET 방식으로 동작

  - '/todo/read?tno=12'와 같이 쿼리 스트링으로 tno 값을 전달

  - TodoService에서 TodoDTO를 반환하고 이를 Controller에서 HttpServletRequest에 담아 JSP에서 출력

// TodoReadController
package org.zerock.jdbcex.controller;

import lombok.extern.log4j.Log4j2;
import org.zerock.jdbcex.dto.TodoDTO;
import org.zerock.jdbcex.service.TodoService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "todoReadController", value = "/todo/read")
@Log4j2
public class TodoReadController extends HttpServlet {
  private TodoService todoService = TodoService.INSTANCE;

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    try {
      Long tno = Long.parseLong(req.getParameter("tno"));
      TodoDTO todoDTO = todoService.get(tno);

      // dto라는 이름으로 TodoDTO 데이터 담기
      req.setAttribute("dto", todoDTO);
      req.getRequestDispatcher("/WEB-INF/todo/read.jsp").forward(req, resp);
    } catch (Exception e){
      log.error(e.getMessage());
      throw new ServletException("read error");
    }
  }
}
<!-- read.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Todo Read</title>
</head>
<body>
  <div>
    <input type = "text" name = "tno" value = "${dto.tno}" readonly>
  </div>
  <div>
    <input type = "text" name = "title" value = "${dto.title}" readonly>
  </div>
  <div>
    <input type = "date" name = "dueDate" value = "${dto.dueDate}">
  </div>
  <div>
    <input type = "checkbox" name = "finished" value = ${dto.finished ? "checked": ""} readonly>
  </div>
<div>
  <!-- 수정/삭제 또는 목록으로 갈 수 있는 링크 -->
  <a href = "/todo/modify?tno=${dto.tno}">Modify/Remove</a>
  <a href = "/todo/list">List</a>
</div>
</body>
</html>

"http://localhost:8080/todo/read?tno=1" 경로로 접속했을 때 tbl_todo 테이블에서 tno가 1인 행의 데이터가 출력

 

  - 목록에서 조회 링크 처리

  - 조회 기능이 정상임이 확인됐으면 목록 페이지에서 각 목록을 누르면 각 게시물로 바로 이동되도록 링크 걸어주기

<!-- list.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri = "http://java.sun.com/jsp/jstl/core" prefix ="c" %>
<html>
<head>
    <title>Todo List</title>
</head>
<body>
<h1>Todo List</h1>
<ul>
    <c:forEach items = "${dtoList}" var = "dto">
        <li>
            // 목록에서 숫자부분을 클릭하면 해당 번호가 쿼리 스트링으로 전달된 read 경로로 조회됨
            <span><a href = "/todo/read?tno=${dto.tno}">${dto.tno}</a></span>
            <span>${dto.title}</span>
            <span>${dto.dueDate}</span>
            <span>${dto.finished? "DONE": "NOT YET"}</span>
        </li>
    </c:forEach>
</ul>
</body>
</html>

각 번호를 누르면 번호에 해당하는 조회 화면으로 이동

 

 4) 수정 / 삭제 기능 구현

  - 두 기능 모두 POST 방식으로 처리

  - 화면에 두 개의 <form> 태그 작성해서 처리 또는 자바스크립트를 이용해 하나의 <form> 태그의 action 속성을 변경해서 처리

 

  - TodoService의 수정 / 삭제 기능 구현

// TodoService
package org.zerock.jdbcex.service;

import com.sun.tools.javac.comp.Todo;
import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.zerock.jdbcex.dao.TodoDAO;
import org.zerock.jdbcex.domain.TodoVO;
import org.zerock.jdbcex.dto.TodoDTO;
import org.zerock.jdbcex.util.MapperUtil;

import java.util.List;
import java.util.stream.Collectors;

@Log4j2
public enum TodoService {
  INSTANCE;

  private TodoDAO dao;
  private ModelMapper modelMapper;

  TodoService() {
    dao = new TodoDAO();
    modelMapper = MapperUtil.INSTANCE.get();

  }

  ...

  // remove() 메서드 작성
  // remove()는 번호만 파라미터로 받아서 해당 번호에 해당하는 데이터 삭제
  public void remove(Long tno) throws Exception {
    log.info("tno: " + tno);
    dao.deleteOne(tno);
  }

  // modify() 메서드 작성
  // modify()는 TodoDTO 타입 전체를 파라미터로 받음
  public void modify(TodoDTO todoDTO) throws Exception {
    log.info("todoDTO: " + todoDTO);
    TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
    dao.updateOne(todoVO);
  }
}

 

  - TodoModifyController의 구현

// TodoModifyController
package org.zerock.jdbcex.controller;

import lombok.extern.log4j.Log4j2;
import org.zerock.jdbcex.dto.TodoDTO;
import org.zerock.jdbcex.service.TodoService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

@WebServlet(name = "todoModifyController", value = "/todo/modify")
@Log4j2
public class TodoModifyController extends HttpServlet{
  private TodoService todoService = TodoService.INSTANCE;
  private final DateTimeFormatter DATEFORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
  
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    try {
      Long tno = Long.parseLong(req.getParameter("tno"));
      TodoDTO todoDTO = todoService.get(tno);
      
      // 데이터 담기
      req.setAttribute("dto", todoDTO);
      req.getRequestDispatcher("/WEB-INF/todo/modify.jsp").forward(req, resp);
    } catch (Exception e) {
      log.error(e.getMessage());
      throw new ServletException("modify get... error");
    }
  }
}

 

  - 수정 작업이 이루어지는 'WEB-INF/todo/modify.jsp'

<!-- modify.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Todo Modify</title>
</head>
<body>

<form id = 'form1' action = '/todo/modify' method = 'post'>
  <div>
    <input type = 'text' name = 'tno' value = '${dto.tno}' readonly>
  </div>
  <div>
    <input type = "text" name = "title" value = "${dto.title}">
  </div>
  <div>
    <input type = "date" name = "dueDate" value = "${dto.dueDate}">
  </div>
  <div>
    <input type = "checkbox" name = "finished" value = ${dto.finished ? "checked": ""}>
  </div>
  <div>
    <button type = 'submit'>Modify</button>
  </div>
</form>

<form id = 'form2' action = '/todo/remove' method = 'post'>
  <input type = 'hidden' name = 'tno' value = '${dto.tno}' readonly>
  <div>
    <button type = 'submit'>Remove</button>
  </div>
</form>

</body>
</html>

 

  - TodoModifyController에서 POST 방식으로 동작하는 doPost() 이용해서 처리

// TodoModifyController
package org.zerock.jdbcex.controller;

import lombok.extern.log4j.Log4j2;
import org.zerock.jdbcex.dto.TodoDTO;
import org.zerock.jdbcex.service.TodoService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

@WebServlet(name = "todoModifyController", value = "/todo/modify")
@Log4j2
public class TodoModifyController extends HttpServlet{
  
  ...
  
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String finishedStr = req.getParameter("finished");

    TodoDTO todoDTO = TodoDTO.builder()
        .tno(Long.parseLong(req.getParameter("tno")))
        .title(req.getParameter("title"))
        .dueDate(LocalDate.parse(req.getParameter("dueDate"), DATEFORMATTER))
        .finished(finishedStr != null && finishedStr.equals("on"))
        .build();
    log.info("/todo/modify POST...");
    log.info(todoDTO);
    try {
      todoService.modify(todoDTO);
    } catch (Exception e) {
      e.printStackTrace();
    }
    resp.sendRedirect("/todo/list");
  }
}

  - <form> 태그에서 전송된 title, finished 등을 이용해서 TodoDTO를 구성

  - 만들어진 TodoDTO는 TodoService 객체로 전달되고 목록 화면으로 다시 이동하여 수정된 결과를 볼 수 있음

 

  - TodoRemoveController의 구현

package org.zerock.jdbcex.controller;

import lombok.extern.log4j.Log4j2;
import org.zerock.jdbcex.service.TodoService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "todoRemoveController", value = "/todo/remove")
@Log4j2
public class TodoRemoveController extends HttpServlet {
  private TodoService todoService = TodoService.INSTANCE;
  
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    Long tno = Long.parseLong(req.getParameter("tno"));
    log.info("tno: "+ tno);
    try {
      todoService.remove(tno);
    } catch (Exception e) {
      log.error(e.getMessage());
      throw new ServletException("read error");
    }
    resp.sendRedirect("/todo/list");
  }
}


 

 1) 코드의 개선 사항들

  • 웹 MVC 구조를 사용하면 확실하게 책임과 역할을 구분할 수 있다는 장점
  • 여러 개의 코드를 만들어야 한다는 단점
  • 개선 사항
    • 여러 개의 Controller를 작성하는 번거로움: TodoDAO나 TodoService와 달리 HttpServlet을 상속하는 여러 개의 Controller 작성해야 함
    • 동일 로직 반복 사용: 게시물 조회나 수정 작업은 둘 다 GET 방식으로 동작, 결과를 보여주는 JSP만 다름, 결국 동일 코드 여러 번 작성
    • 예외 처리 부재: 예외 발생 시 처리에 대한 설계가 없어 비정상적 호출 발생 시 대비 x
    • 반복적 메서드 호출: HttpServletRequset나 HttpServletResponse를 이용 해 TodoDTO를 구성하는 작업 등이 동일한 코드들로 작성되어 개선 필요, Long.parseLong() 드으이 코드도 많이 반복
    • 자바의 객체지향 기법을 좀 더 사용할 필요 o

+ Recent posts