[Spring] Todo 추가 기능 만들기 (2) - TodoController, TodoService 리팩토링 + model 의미

민지·2024년 1월 24일
0

이번 포스팅에서는 이전 포스팅에 이어, 사용자가 입력한 세부정보(description)을 Todo 리스트에 추가해, 화면에 보여지게 한 과정을 정리하겠다.

새롭게 배우는 내용은 없고, 이미 배운 기능들로 기존의 코드들을 리팩토링하는 과정을 담았다.

1. addNewTodoPage()메서드에 @RequestParam을 추가하는 이유


다음 사진과 같이, /add-todo에서 description을 submit했더니,

  • header : POST request method를 쓴 것과

  • add-todo에서의 Payload탭 : description에서 입력한 "learn web Crawling"이 전달된 사실을 확인할 수 있다.

  • 참고로 /add-todo submit 결과, redirect:list-todos URL에서의 request method는 아직 아무것도 누르지 않았기에 GET method를 사용한 것을 확인할 수 있었다.

위에서 관찰한 사실을 바탕으로,
POST method임과 동시에, /add-todo URL에서, @RequestParam을 통해 Payload의 data를 catch할 수 있음을 생각해낼 수 있다!

// add-todo - POST
	@RequestMapping(value = "/add-todo", method = RequestMethod.POST)
	public String addNewTodoPage(@RequestParam String description) {

		return "redirect:list-todos";
	}

2. TodoService 리팩토링 : Todo를 추가하기 위한 메서드, addTodo()

잠깐 todo 패키지 구조 참고


위와 같은 todo 패키지에는, Todo에 넣은 정보를 멤버변수 및 생성자 등등의 함수로 담은 Todo.java클래스와, request URL별로 mapping해주는 각 코드에 model과 view를 리턴해주는 컨트롤러 코드를 모은 TodoController클래스, Todo기능과 관련된 그외의 논리로직을 담아 깔끔하게 정리할 수 있게 하는 TodoService.java 클래스가 있다.

그렇기에, Todo를 추가하기 위한 메서드인 addTodo()메서드는 TodoService클래스에 추가해준다.

@Service
public class TodoService {
	private static List<Todo> todos = new ArrayList<>();

	private static int todosCount = 0;

	// static 변수 초기화를 위해서는, static 블럭이 필요.
	static {
		todos.add(new Todo(++todosCount, "minjiki2", "Learn springboot",

				LocalDate.now().plusYears(1), false));
		todos.add(new Todo(++todosCount, "minjiki2", "Learn db",

				LocalDate.now().plusYears(1), false));
		todos.add(new Todo(++todosCount, "minjiki2", "Learn algorithm",

				LocalDate.now().plusYears(2), false));

	}

	public List<Todo> findByUsername(String userName) {
		return todos;
	}

	public void AddTodo(String username, String description, LocalDate deadline, boolean done) {

		Todo todo = new Todo(++todosCount, username, description, deadline, done);
		todos.add(todo);

	}
}

todosCount private변수를 추가해 id에 넣으며 todo의 개수를 세주었고,
AddTodo()메서드에서는 입력받은 매개변수들을 이용해 todo 객체를 만들어, List인 todos에 추가하게끔 했다.

3. TodoController 리팩토링 : model의 의미, AddTodo()메서드 이용

// add-todo - POST 일부분만 발췌
	private TodoService todoService;

	@Autowired
	public TodoController(TodoService todoService) {
		super();
		this.todoService = todoService;
	}
    
	@RequestMapping(value = "/add-todo", method = RequestMethod.POST)
	public String addNewTodoPage(@RequestParam String description, ModelMap model) {
        String username = (String)model.get("name");
        todoService.AddTodo(username, description, LocalDate.now().plusYears(1), false);
		return "redirect:list-todos";
	}
  • todoService.AddTodo((String) model.get("name"), description, null, false); 코드의 의미
    • LoginController에서 사용자 정보 name을 @SessionAttributes("name"); 을 통해 model에 넣으며 + session scope로 저장된 것이 있다.
      해당 model에 "name" 속성명으로 저장된 사용자 String 값을 가져오기 위해 (String)model.get("name");을 사용한 것
// 수정 안한, 기존의 listTodo.jsp 코드
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
	<head>
		<meta charset="UTF-8">
		<link href = "webjars/bootstrap/5.1.3/css/bootstrap.min.css" rel = "stylesheet">
		<title>List Todos Page</title>
	</head>
	<body>
		<div class = "container">
			<h1>Your Todos</h1>
	 		<table class = "table">
				<thead>
					<tr>
						<th>id</th>
						<th>Description</th>
						<th>Deadline</th>
						<th>is Done?</th>
					</tr>
				</thead>
				<tbody>
					<c:forEach items = "${todos}" var = "todo">
						<tr>
							<td>${todo.id}</td>
							<td>${todo.description}</td>
							<td>${todo.deadline}</td>
							<td>${todo.done}</td>
						</tr>
					</c:forEach>
				</tbody>
			</table>
			<a href = "add-todo" class = "btn btn-success">Add Todo</a>
			<script src="webjars/bootstrap/5.1.3/js/bootstrap.min.js"></script>
			<script src="webjars/jquery/3.6.0/jquery.min.js"></script>
		</div>
	</body>
</html>

addNewTodoPage()메서드에서 @RequestParam으로 가져온 description값을 리팩토링한 TodoService의 AddTodo를 통해 todos 객체에 넣어두었다.

listTodos.jsp를 보면, 해당 뷰는 todo 패키지 하위의 @Service를 붙인 TodoService에 추가된 todos들을 가져오는 코드이다.

참고로, listTodos.jsp에서 ${todos}는 /list-todos URL과 매핑된 listAllTodos에서 추가한 모델명이다. 해당 모델에는 findByUsername()메서드로 추가된 3개의 정적 데이터값이 있었는데, addNewTodoPage()에서 TodoService를 리팩토링해 AddTodo메서드를 통해 추가한 todo까지 값이 들어갔다. 이때 addNewTodoPage가 /add-todo URL로 매핑되었는데, /list-todos URL에 저장된 model인 "todos"를 써먹을 수 있었던 이유는, addNewTodoPage()메서드가 "redirect:list-todos"를 리턴하기 때문이다!!

최종 코드

위의 설명을 알아듣기 쉽게 수정한 코드를 모아놓았다.

수정 안한, 기존의 listTodos.jsp코드와 리팩토링한 TodoService.java 코드이다.

// 수정 안한, 기존의 listTodo.jsp 코드
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
	<head>
		<meta charset="UTF-8">
		<link href = "webjars/bootstrap/5.1.3/css/bootstrap.min.css" rel = "stylesheet">
		<title>List Todos Page</title>
	</head>
	<body>
		<div class = "container">
			<h1>Your Todos</h1>
	 		<table class = "table">
				<thead>
					<tr>
						<th>id</th>
						<th>Description</th>
						<th>Deadline</th>
						<th>is Done?</th>
					</tr>
				</thead>
				<tbody>
					<c:forEach items = "${todos}" var = "todo">
						<tr>
							<td>${todo.id}</td>
							<td>${todo.description}</td>
							<td>${todo.deadline}</td>
							<td>${todo.done}</td>
						</tr>
					</c:forEach>
				</tbody>
			</table>
			<a href = "add-todo" class = "btn btn-success">Add Todo</a>
			<script src="webjars/bootstrap/5.1.3/js/bootstrap.min.js"></script>
			<script src="webjars/jquery/3.6.0/jquery.min.js"></script>
		</div>
	</body>
</html>

// AddTodo메서드를 추가하며 리팩토링한 TodoService
@Service
public class TodoService {
	private static List<Todo> todos = new ArrayList<>();

	private static int todosCount = 0;

	// static 변수 초기화를 위해서는, static 블럭이 필요.
	static {
		todos.add(new Todo(++todosCount, "minjiki2", "Learn springboot",

				LocalDate.now().plusYears(1), false));
		todos.add(new Todo(++todosCount, "minjiki2", "Learn db",

				LocalDate.now().plusYears(1), false));
		todos.add(new Todo(++todosCount, "minjiki2", "Learn algorithm",

				LocalDate.now().plusYears(2), false));

	}

	public List<Todo> findByUsername(String userName) {
		return todos;
	}

	public void AddTodo(String username, String description, LocalDate deadline, boolean done) {

		Todo todo = new Todo(++todosCount, username, description, deadline, done);
		todos.add(todo);

	}
}
// TodoController
@Controller
@SessionAttributes("name")
public class TodoController {

	private TodoService todoService;

	@Autowired
	public TodoController(TodoService todoService) {
		super();
		this.todoService = todoService;
	}

	// list-todos
	@RequestMapping("/list-todos")
	public String listAllTodos(ModelMap model) {

		List<Todo> todos = todoService.findByUsername("minjiki2");

		model.addAttribute("todos", todos);

		return "listTodos";
	}

	// add-todo - GET
	@RequestMapping(value = "/add-todo", method = RequestMethod.GET)
	public String showNewTodoPage() {
		return "addTodo";
	}

	// add-todo - POST
	@RequestMapping(value = "/add-todo", method = RequestMethod.POST)
	public String addNewTodoPage(@RequestParam String description, ModelMap model) {
        String username = (String)model.get("name");
        todoService.AddTodo(username, description, LocalDate.now().plusYears(1), false);
		return "redirect:list-todos";
	}
}

실행결과


참고로 위 Your Todos의 링크는

하지만..

하지만 해당 추가된 todos는 static 지역변수이기에 서버가 재시작될 때마다 기존에 저장된 값들이 초기화된다. 즉, 아무리 10개 100개의 todo를 입력해봤자, 서버를 재시작하면, 기존에 초기화된 3개의 값만 남는다. 후에 영구적인 데이터베이스를 넣기 위해 MySQL을 배워, 해당 코드를 수정할 것이다.

또한, 만일 Enter Todo Details에서 다음과 같이 공백을 제출해도, 공백이 그대로 list에 add되는 문제사항을 발견했다. 다음 포스팅에서 검증작업을 추가해, 아래와 같은 일을 막을 것이다.




이 시리즈는 Udemy 강의의 내용을 정리한 것입니다.
https://www.udemy.com/course/spring-boot-and-spring-framework-korean/

profile
배운 내용을 바로바로 기록하자!

0개의 댓글