1편에 이어서 생성, 완료, 삭제를 구현해보자.
1편 링크: https://takethat.tistory.com/80
1. 생성
📄 TodoController.java
PostMapping("/todo/create")
public String create(
@RequestParam("todo-desc") String content) {
// 새로운 TODO를 생성하는 컨트롤러 메소드
todoService.createToDo(content);
return "redirect:/todo";
}
우선 MVC 패턴을 이해할 필요가 있는데, Controller에서는 기능을 구현하지 않는다.
대신 요청한 웹사이트를 서비스하기 위해서 모델을 호출하는데, 그래서 보통 서비스라고 명칭을 짓고 있는 것
POST 형식으로 /todo/create를 매핑하여 @RequestParam을 통해 html 파일에서 데이터를 가져온다.
<form th:action="@{/todo/create}" method="post">
위에 코드는 html 파일에 있던 코드인데 위에서 설명한대로 매핑이 되어있다.
그리고 name이 "todo-desc"인 데이터를 가져와 content라는 String 형식의 데이터로 가져오게 된다.
왜 content만 가져왔냐면, id 자체는 1L부터 1씩 계속 추가되게 제작할 것이고, 할 일이 등록되면 완료(true) 상태가 아니기 때문에 content만 가져오게 했다. 이건 Service 기능과 같이보자.
📄 TodoService.java
public void createToDo(String content) {
TodoDto newTodo = new TodoDto(nextId, content, false);
nextId++;
todoList.add(newTodo);
System.out.println(todoList);
}
Dto 형식을 지키는 새로운 dto를 생성하는데, 아까 말한 것 처럼 id, false를 기본적으로 주고, 가져온 content만 추가해주면 된다.
그 후 생성 메서드만 가져온다고 생략되어 있지만 저장하려고 만들어놨던 어레이리스트인 todoList에 추가(저장)해준다.
그리고 다음 가져올 할 일의 번호는 2여야 하니까 1을 더해주고, 우리는 lombok의 @Slf4j, 아니면 logger를 임포트하지 않았기 때문에 확인 겸 println으로 출력하게 만들었다.(사실 성능 문제로 로그로 하는 게 좋다.)
전 게시물에도 설명했지만, todoList에만 저장된다면 우리가 보는 html 파일에서 자연스레 결과를 보여줘야 한다.
이제 그걸 새로고침 한다는 느낌으로 Controller에서 redirect로 반환하는 것
이렇게 제작하면 결과는 아래와 같다.
이걸로 Todo를 생성하는 것은 만들어 봤는데, 이제 완료나 삭제를 추가해보자.
2. 완료
📄 TodoController.java
@PostMapping("/todo/{id}/update")
public String update(@PathVariable("id") Long id) {
// TODO의 done 상태를 변경하는 메소드
TodoDto todoDto = todoService.updateToDo(id);
return "redirect:/todo";
}
보통 상태를 변경할 때는 PutMapping도 사용하지만, 여기선 PostMapping으로 진행했다.
이제 @PathVariable의 경우 url에 {id} 부분에 들어갈 내용을 처리해줄 수 있다.
그리고 그 id값이 있어야 todoList에서 해당 todo를 찾을 수 있기 때문에 가져와서 dto 형식에 맞춰 반환해준다.
사실 컨트롤러에서는 todoService.updateToDo()만 진행하고, 반환을 void로 변경한 다음 TodoDto 부분은 Service에서 다뤄야 하는게 맞는 것 같은데, 수정하기 귀찮아서 그대로 진행했다.ㅎ
그리고 연습이니까~
📄 TodoService.java
public TodoDto updateToDo(Long id) {
return todoList
.stream()
.filter(todoDto -> todoDto.getId().equals(id))
.peek(todoDto -> todoDto.setDone(!todoDto.getDone()))
.findFirst()
.orElse(null);
}
갑자기 뜬금없이 stream이 나오는데 이건 내가 연습해보려고 만들어봤다.
스트림은 '생성 -> 매핑, 필터링 -> 결과'의 과정을 가지게 된다고 생각하면 편하다.
그래서 .filter 부분에서 가져온 파라미터 값인 id와 일치하는지 todoList의 id들과 비교해서 가져온다.
.peek 부분에서 getDone(), 즉 현재 담겨있는 bool 값을 다시 !(not)을 붙여 true <-> false 변환이 가능하게 만들었다.
Done 상태에서도 다시 todo로 돌아갈 수 있게 만들기 위해서 서로간의 변환이 되게끔했다.
.findFirst()는 그냥 한 요소(element)를 가져오는데 병렬구조(stream을 사용하는 이유 중 하나)에서 순서를 고려해 제일 앞에 있는 요소를 가져오는 것(자매품으로는 findany가 있는데, 해당하는 값이 여러 개라면 리턴 값이 계속 달라진다.)
.orElse()는 그냥 해당 값없으면 괄호 안에 있는 값을 나오게 하는 것
하여튼 스트림에는 참 많은 기능들이 있는데, 이 todo를 만들때부터 계속해서 연습하고 있다.
이제 결과를 살펴보자.
3. 삭제
📄 TodoController.java
@PostMapping("/todo/{id}/delete")
public String delete(@PathVariable("id") Long id) {
// TODO를 삭제하는 메소드
todoService.deleteToDo(id);
return "redirect:/todo";
}
완료 기능이랑 주소만 다르고 service의 deleteToDo 메서드를 사용할 뿐 거의 동일한 코드
📄 TodoService.java
public boolean deleteToDo(Long id) {
OptionalInt idx = IntStream
.range(0, todoList.size())
.filter(i -> todoList.get(i).getId().equals(id))
.findFirst();
if (idx.isPresent()) {
todoList.remove(idx.getAsInt());
return true;
}
return false; // Not Found
}
우선 OptionalInt 형식에서 IntStream을 이용했다.
OptionalInt는 Optional의 int 형이라고 생각하면 편하고, IntStream도 int형 스트림이라고 생각하면 편하다.
이 부분은 다른 블로그들이 정리를 참 잘해서 찾아보면 될 것 같다ㅎ.ㅎ
range에서 설정한 인덱스 범위 안에서 filter와 findFirst는 완료 부분에서 다룬거랑 거의 같은 역할을 한다.
아 참고로 i는 IntStream의 인덱스 값을 나타낸다.
근데 찾은 값이 존재하지 않으면 false, 존재하면 remove를 통해 삭제하는 것.
이제 결과를 살펴보자.
여기까지 진짜~~ 간단하게 todo를 만들어 봤다.
어차피 나 혼자 만드는게 목적이여서 스트림 부분도 찾아보면서 연습해봤었다.
근데 아마 TODO에 있는 첫번째(nextId 값이 1) 값이 Done으로 안가는데 이 부분은 Stream을 잘못 쓴 내 잘못...
나중에 고쳐보게 된다면 이 글을 수정하지 않을까 싶다.
요새 Spring Security, Oauth 이 부분 배우고 있는데 넘 어려워서 더 공부하느라 정신이 없다...
그래서 블로그 포스팅이 너무 줄었는데 좀 더 노력해봐야겠다... ㅜㅜ
+ 추가
저거 첫 번째로 생성한 객체는 왜 상태변환이 안될까 신경쓰여서 여러가지 시도를 해봤다.
1. Service 부분의 Stream을 intStream으로 수정 -> 안됨
2. 1번 인덱스의 todoDto 생성 후 2번부터 html에 표시되고 작동하게 끔 수정 -> 안됨
사실 저거 버튼 누를때만 '~todo/?'로 나오는 걸 봐선 인자 값을 잘못 전달하는 것 같다.(추측, 근데 또 보면 내눈엔 문제 없음)
뭐 그 외 짜잘한 시도는 해봤는데 다 안되서 그냥 냅뒀다.
근데 또 Postman에서 확인하면 잘 돌아간다...
간단한거 만들면서도 이렇게 문제가 있으니,, 더 열심히 공부해야겠다,..
부끄러워서 별로 코드는 공개안하고 싶지만 혹시나 해서 깃허브 링크 추가