사이먼's 코딩노트

[SpringBoot] 유효성 체크 및 에러 처리(2) 본문

Java/SpringBoot

[SpringBoot] 유효성 체크 및 에러 처리(2)

simonpark817 2024. 5. 12. 21:03

[유효성 체크 및 에러 처리]

  • 질문 등록 시 제목이나 내용을 입력하지 않고 질문이 등록되는것을 방지하고자 폼 클래스를 통해 유효성 체크하는 기능을 추가하고 로컬 서버를 재실행하면 에러 메시지가 붉은 색으로 표시되는 것을 확인할 수 있다.
  • 하지만 여기서 한 가지 문제를 발견했다.
  • 예를 들어 제목을 입력하고 내용을 비워둔 채로 '저장하기' 버튼을 클릭하면 에러 메시지가 표시되면서 이미 입력했던 제목 텍스트가 사라진다는 점이다.
  • 이번에는 입력한 제목이나 내용의 텍스트가 남아있도록 에러 처리를 해봅시다.
  • 해당 에러를 처리하기 위해 아래는 question_form.html 템플릿에서 코드를 수정한 모습이다.
<html layout:decorate="~{layout}">
<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 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>
  • 코드가 수정된 부분은 총 2군데이고, 각 제목과 내용의 입력란인 input 태그와 textarea 태그의 속성 값을 수정하였다.
  • 기존에는 name="subject" id="subject" 와 name="content" id="content" 를 없애고 th:field="*{subject}" 와 th:field="*{content}를 추가하였다.
  • 이렇게 하면 해당 태그의 name, id, value 속성이 모두 자동으로 생성되고 타임리프가 value 속성에 기존에 입력된 값을 채워 넣어 오류가 발생하더라도 기존에 입력한 값이 유지된다.
  • 만약 th:field 속성을 사용하기 싫다하면 name, id 속성은 그대로 남겨두고, value 속성을 추가하면 된다.
  • 해당 코드를 적용했다면 이제는 제목 또는 내용에 입력한 값은 계속 유지될 것이다.

 

[답변 등록 유효성 체크]

  • 지금까지는 질문 등록 부분에만 QuestionForm.java 클래스를 생성하여 유효성 체크를 적용하였다면, 이번에는 답변 등록 부분에도 동일한 유효성 체크를 AnswerForm.java 클래스를 생성하여 적용해봅시다.
  • 아래는 답변 등록하는 부분에 유효성 체크 확인을 위한 AnswerForm.java 클래스를 생성하여 아래와 같이 코드를 추가해봅시다.
package com.sbs.sbb.Answer;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class AnswerForm {
    @NotBlank(message="내용은 필수항목입니다.")
    @Size(max=20000, message = "제목을 20,000자 이하로 입력해주세요.")
    private String content;
}
  • 질문 등록 부분과는 다른 점은 답변 등록에는 내용만 포함되기 때문에 content 변수 하나만을 선언하였다.
  • content 속성에는 @NotBlank와 @Size 어노테이션을 적용하여 에러 발생 시 표시되는 메시지를 작성하였다.
  • 다음은 AnswerForm을 컨트롤러에서 사용할 수 있도록 아래와 같이 AnswerController.java 클래스에 코드를 수정해봅시다.
@PostMapping("/create/{id}")
public String createAnswer(Model model, @PathVariable("id") Integer id, @Valid AnswerForm answerForm, BindingResult bindingResult) {
    Question q = this.questionService.getQuestion(id);

    if(bindingResult.hasErrors()) {
        model.addAttribute("question", q);
        return "question_detail";
    }

    Answer answer = this.answerService.create(q, answerForm.getContent());

    return "redirect:/question/detail/%d".formatted(id);
}
  • 유효성 체크를 위해서 @Valid 어노테이션을 적용하였고, 이를 적용함으로써 AnswerForm의 @NotBlank, @Size 등으로 설정한 검증 기능이 동작하도록 하였다.
  • 검증이 수행된 결과를 의미하는 객체인 BindingResult도 @Valid 어노테이션 뒤에 매개변수로 받아오면서 bindingResult.hasErrors() 메서드를 조건문을 통해 호출하여 에러가 있는 경우 다시 입력을 하도록하고 에러가 없을 때 답변이 저장되도록 하였다.
  • 화면 페이지에 에러 메시지가 잘 표시가 되도록 답변 등록하는 페이지인 question_detail.html 템플릿에 아래와 같이 코드를 수정하였다. 
<form th:action="@{|/answer/create/${question.id}|}" th:object="${answerForm}" method="post" class="my-3">
    <div class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}">
        <div th:each="err : ${#fields.allErrors()}" th:text="${err}" />
    </div>
    <textarea th:field="*{content}" rows="10" class="form-control"></textarea>
    <input type="submit" value="답변등록" class="btn btn-primary my-2">
</form>
  • 위 코드는 question_detail.html 중 가장 하단의 textarea 태그를 통해 내용을 입력하는 부분과 '답변등록' 버튼이 구현된 부분이다.
  • 질문 작성때와 마찬가지로 답변 등록 form의 입력 항목과 AnswerForm을 타임리프에 연결하기 위해 th:object 속성을 추가하였다.
  • 그리고 검증이 실패할 경우 #fields.hasAnyErrors()와 #fields.allErrors()를 사용하여 에러 메시지를 표시하도록 하였고, 작성된 내용의 텍스트가 남아있도록 th:field="*{content}" 속성 값을 추가하였다.
  • 위의 나열된 모든 코드들을 각 클래스에 작성하고 로컬 서버를 다시 실행해보면 답변 등록 시에도 폼 클래스를 이용한 유효성 체크가 적용되었을 것이다.

답변 내용을 입력하지 않았을 때

 

[프론트단에서 직접 유효성 체크]

  • 지금까지 적용한 것처럼 폼 클래스를 사용하여 유효성 체크를 할 수 있지만, 사실 폼 필드 속성으로 HTML인 프론트단에서 직접 유효성 체크를 할 수도 있다.
  • 아래는 HTML에서 직접 유효성 체크가 가능하도록 바로 위에서 적용한 question_detail.html 템플릿에서 코드를 추가하였다.
<textarea required maxlength="20000" placeholder="내용(20,000자 이하)" th:field="*{content}" rows="10" class="form-control"></textarea>
  • 위 코드와 같이 유효성을 체크하고자 하는 태그 안에서 속성 값을 required를 지정하면 프론트단에서 직접 체크를 할 수 있다.
  • required maxlength="20000"은 최대 20,000자 까지라는 뜻이고, 아무것도 작성하지 않을 때도 유효성 체크를 자동으로 검사하게 된다.
  • 해당 코드를 적용하여 로컬 서버를 다시 실행해보면 이제는 붉은 화면이 아닌 하나의 아이콘과 함께 경고 텍스트가 화면에 표시된다.

HTML에서 적용한 유효성 체크

 

[에러 출력 부분 공통 템플릿 적용] 

  • 마지막으로 에러 메시지를 출력하는 HTML 코드는 질문, 답변 등록에서 모두 반복해서 사용되고 있어 공통 템플릿으로 만들어봅시다.
  • 아래 코드는 question_form.html과 question_detail.html에서 공통적으로 사용된 에러 메시지 출력 코드이다.
<div class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}">
    <div th:each="err : ${#fields.allErrors()}" th:text="${err}" />
</div>
  • 중복을 막는 것은 앞으로 코드를 구현하거나 유지보수를 할 때 굉장히 중요한 부분이다.
  • 앞으로 추가로 만들 템플릿에도 이와 같은 에러를 표시하는 부분이 필요할 것이고, 더 편리하게 관리하기 위해서 에러 메시지 출력 부분의 부모 격인 템플릿을 생성해봅시다.
  • 아래는 form_erros.html 라는 템플릿을 새로 생성하여 작성한 코드이다.
<div th:fragment="formErrorsFragment" class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}">
    <div th:each="err : ${#fields.allErrors()}" th:text="${err}" />
</div>
  • 상위 div 태그에 th:fragment="formErrorsFragment" 속성을 추가하여 다른 템플릿에서 해당 영역을 사용할 수 있도록 설정하였다.
  • question_form.html와 question_detail.html 템플릿에 해당 부모 템플릿을 상속받으려면 에러 메시지가 출력되는 태그 자리에 아래의 코드를 대체하면된다.
<div th:replace="~{form_errors :: formErrorsFragment}"></div>

 

반응형