4주차 과제로 이제 질문 등록 폼과 질문을 등록하는 기능을 만들어야 한다.
<a> ... </a>
)에 부트스트랩의 btn btn-primary
클래스를 적용경로: src\main\resources\templates\question_detail.html
<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
<table class="table">
(... 생략 ...)
</table>
<a th:href="@{/question/create}" class="btn btn-primary">질문 등록하기</a>
</div>
</html>
/question/create
요청은 GET 요청에 해당하므로 @GetMapping 어노테이션 사용(...생략...)
@RequestMapping("/question")
@RequiredArgsConstructor //questionRepository 속성을 포함하는 생성자를 자동으로 롬복에서 생성
@Controller
public class QuestionController {
(...생략...)
@GetMapping("/create")
public String questionCreate(){
return "question_form";
}
}
<form method=”post”>
에 의해 POST 방식으로 데이터가 요청됨<html layout:decorate="~{layout}" xmlns:layout="http://www.w3.org/1999/xhtml">
<div layout:fragment="content" class="container">
<h5 class="my-3 border-bottom pb-2">질문등록</h5>
<form th:action="@{/question/create}" method="post">
<div class="mb-3">
<label for="subject" class="form-label">제목</label>
<input type="text" name="subject" id="subject" class="form-control">
</div>
<div class="mb-3">
<label for="content" class="form-label">내용</label>
<textarea name="content" id="content" class="form-control" rows="10"></textarea>
</div>
<input type="submit" value="저장하기" class="btn btn-primary my-2">
</form>
</div>
</html>
/question/create
URL을 처리하기 위해 @PostMapping 어노테이션을 지정한 questionCreate 메서드를 추가경로: src\main\java\com\gdsc\webboard\question\QuestionController.java
(...생략...)
@RequestMapping("/question")
@RequiredArgsConstructor //questionRepository 속성을 포함하는 생성자를 자동으로 롬복에서 생성
@Controller
public class QuestionController {
private final QuestionService questionService;
(...생략...)
@PostMapping("/create")
public String questionCreate(@RequestParam String subject, @RequestParam String content) {
this.questionService.create(subject, content);
return "redirect:/question/list"; // 질문 저장후 질문목록으로 이동
}
}
경로: src\main\java\com\gdsc\webboard\question\QuestionService.java
(... 생략 ...)
import java.time.LocalDateTime;
(... 생략 ...)
public class QuestionService {
(... 생략 ...)
public void create(String subject, String content) {
Question q = new Question();
q.setSubject(subject);
q.setContent(content);
q.setCreateDate(LocalDateTime.now());
this.questionRepository.save(q);
}
}
package com.gdsc.webboard.question;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class QuestionForm {
@NotEmpty(message="제목은 필수항목입니다.")
@Size(max=200)
private String subject;
@NotEmpty(message="내용은 필수항목입니다.")
private String content;
}
그다음, 컨트롤러의 questionCreate 메서드의 매개변수를 QuestionFrom 객체로 변경한다.
(... 생략 ...)
import jakarta.validation.Valid;
import org.springframework.validation.BindingResult;
(... 생략 ...)
public class QuestionController {
(... 생략 ...)
@PostMapping("/create")
public String questionCreate(@Valid QuestionForm questionForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "question_form";
}
this.questionService.create(questionForm.getSubject(), questionForm.getContent());
return "redirect:/question/list";
}
}
@Valid
: QuestionForm의 @NotEmpty, @Size 등으로 설정한 검증 기능이 동작bindResult.hasErrors()
를 호출하여 오류가 있는 경우에는 다시 폼을 작성하는 화면을 렌더링하게 했고 오류가 없을 경우에만 질문 등록이 진행검증에 실패한 오류메시지를 보여주기 위해 템플릿을 수정한다.
<html layout:decorate="~{layout}" xmlns:layout="http://www.w3.org/1999/xhtml">
<div layout:fragment="content" class="container">
<h5 class="my-3 border-bottom pb-2">질문등록</h5>
<form th:action="@{/question/create}" th:object="${questionForm}" method="post">
<div class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}">
<div th:each="err : ${#fields.allErrors()}" th:text="${err}"></div>
</div>
<div class="mb-3">
<label for="subject" class="form-label">제목</label>
<input type="text" th:field="*{subject}" class="form-control">
</div>
<div class="mb-3">
<label for="content" class="form-label">내용</label>
<textarea th:field="*{content}" class="form-control" rows="10"></textarea>
</div>
<input type="submit" value="저장하기" class="btn btn-primary my-2">
</form>
</div>
</html>
#fields.hasAnyErrors
가 true인 경우는 QuestionForm 검증이 실패한 경우#fields.allErrors()
: QuestionForm에서 검증에 실패한 오류 메시지name="subject"
, name="content"
와 같이 사용하던 부분을 위와 같이 th:field
속성을 사용하도록 변경하여 타임리프가 value 속성에 기존 값을 채워 넣어 오류가 발생하더라도 기존에 입력한 값이 유지됨