본문 바로가기

Spring

[Spring]Spring 시작해보기(12) - 질문 등록 버튼 및 폼 생성

질문들의 List는 출력하지만, 질문을 등록할 수 있는 방법은 DB에서 직접 적어 넣을 수 있는 방법밖에는 없다.

 

하지만, <form>을 사용해 질문을 직접 web application에서 만들 수 있다.

 

1. 질문 등록하기 버튼 생성

question_list.html 파일 가장 아래 button을 넣을 곳에 아래 코드를 넣어준다.

<a th:href="@{/question/create}" class="btn btn-primary">질문 등록하기</a>

bootstrap을 사용한 버튼이 생성되고, question/create로 Link되었다.

 

2. URL Mapping

우리는 question/create란 URL로 이동할 수 없다. 왜냐하면 Controller에서 Mapping을 해주지 않았기 때문이다.

 

Mapping을 위해 QuestionController에 아래 코드를 적어주자.

@GetMapping("/create")
public String questionCreate() {
    return "question_form";
}

@GetMapping은 GET요청만 받을 수 있고, URL에 /question을 넣지 않은 이유는 class의 URL Prefix로 /question을 넣어주었기 때문이다.

 

이 questionCreate메서드는 question_form을 랜더링 해준다.

 

 

그러기 위해서는

3. question_form.html 파일 생성

layout template을 상속해주고, 질문을 등록할 수 있는 form을 생성한다.

<html layout:decorate="~{layout}">
<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" id="subject" name="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>

여기서 label의 for는 label클릭 시 label의 for과 같은 이름의 id를 활성화 시킬 수 있다.

 

하지만, submit을 할 경우 저장이 되지 않고 오류가 발생하는데, GET방식을 Mapping했지만, 보내는 것은 POST방식으로 보내주기 때문이다.

 

고로 GET으로 받아오는 것이 없어 오류가 발생하는 것이다.

 

 

4. POST 요청 받을 수 있도록 수정

POST요청을 받을 수 있도록 수정해보자.

 

POST요청을 받기 위해 Controller에서 PostMapping을 진행해보자.

그리고, 위와 다르게 <form>에서 받아온 파라미터를 받아온다.

@PostMapping("/create")
public String questionCreate(@RequestParam String subject, @RequestParam String content) {
    // TODO 질문을 저장한다.
    return "redirect:/question/list"; // 질문 저장후 질문목록으로 이동
}

이제 질문 저장의 기능을 동작하게 하는 시간이다.

 

5. 서비스에 기능 추가

질문 엔티티에 객체를 새로 추가할 수 있도록 설정자를 사용한다.

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);
}

그 후 Controller에서 service로 만든 기능을 추가한다.

@PostMapping("/create")
public String questionCreate(@RequestParam String subject, @RequestParam String content) {
    this.questionService.create(subject, content);
    return "redirect:/question/list";
}

 

 

++ <form>의 빈 값을 보내지 않도록 하기위한 방법

spring boot에 validation이라는 라이브러리가 있다. 이것을 이용해 검증해보자.

implementation 'org.springframework.boot:spring-boot-starter-validation'

이 라이브러리를 이용하면 다음과 같은 어노테이션을 사용할 수 있다.

이제 화면에서 전달되는 입력 값을 검증하기 위해 form class가 새로 필요하다.

package com.mysite.sbb.question;

import javax.validation.constraints.NotEmpty;
import javax.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;
}

 

QuestionForm을 Controller에서 동작할 수 있도록 수정한다.

@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";
}

Controller에 매개변수를 파라미터로 보낸 subject와 content가 아닌 QuestionForm 객체로 보내면, subject와 content를 가진 폼이 전송될 경우 QuestionForm의 subject와 content 속성이 자동으로 바인딩이 된다.

 

QuestionForm 앞에 @Valid 어노테이션을 적용하면 QuestionForm 내 @NotEmpty와 @Size 어노테이션이 검증해준다.

 

BindingResult 객체는 @Valid 어노테이션으로 인해 검증이 수행된 결과를 의미한다.

 

그러면 폼이 있는 웹에서도 오류메시지를 출력시킬 수 있도록 하면 어떨까?

그러기 위해서 아래 코드를 입력한다.

<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 class="mb-3">
      <label for="subject" class="form-label">제목</label>
.....
...

th:object속성을 사용하여 폼의 속성들이 QuestionForm 속성으로 구성된다는 것을 thymeleaf 엔진에 알리고 시작한다.

 

th:if="${#fields.hasAnyErrors()}" 는 true가 검증에 실패한 경우, false는 검증에 성공한 경우를 의미한다. 만약 검증에 실패할 경우 해당 영역에 표시될 수 있도록 했다.

 

th:each="err : ${#fields.allErrors()}" th:text="${err}" 는 검증에 실패한 모든 오류를 출력시키는 것을 의미한다.

 

GET요청을 받았을 경우에도 th:object 속성으로 인해 QuestionForm 객체가 필요하여 파라미터로 넣어주어야 한다.

 

 

+++ 오류 발생 시 값 지워지는 것 방지하기

오류가 발생하면 이미 입력한 값들이 다 지워지는 현상이 발생한다.

그러기 위해서는 어떻게 해야할까?

 

간단한 template 수정을 통해 해결할 수 있다.

 

name 속성을 th:field 속성으로 변경하여 속성들이 모두 자동으로 생성되고, 기존 값을 채워 넣어 유지할 수 있다.