본문 바로가기
프로그래밍/Springboot-토이프로젝트

【게시판-번외05】유효성 검사

by 코이킹 2022. 9. 14.
반응형

안녕하세요 코이킹입니다.
이번 포스트는 유효성 검사에 대한 내용입니다. 


1. 목표

- 유효성 검사 / 정규표현식 등의 키워드를 알기

- 스프링 부트의 유효성 검사기능을 사용하여 컨트롤러에서 유효성 검사를 할 수 있다. 

 

2. 유효성 검사란?

유효성 검사는 데이터가 사양대로 입력되었는지 확인하여, 어플리케이션의 이상 동작을 미연에 방지해주는 것을 말합니다. 

웹 어플리케이션구현시에는 요청 파라미터 / 요청 바디의 데이터에 대해서 주로 유효성 검사를 실시합니다. 

유효성 검사는 조건문등으로 Null체크나 미리 지정한 상수를 비교하거나 ・ 정규표현식으로 정해진 패턴에 해당하는 데이터인지 데이터의 패턴을 확인하는 방식으로 이루어집니다. 

스프링 부트에서는 유효성 검사를 쉽게 구현할 수 있도록 해주는 기능을 제공하고 있으므로, 스프링 부트의 기능을 사용해서 유효성 검사를 구현해 보겠습니다. 

 

3. 유효성 검사 구현 

1) 의존성 라이브러리 추가 : /template-springboot/build.gradle

plugins {
	id 'org.springframework.boot' version '2.6.8'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.sb.template'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}

	all*.exclude module : 'spring-boot-starter-logging'
}

repositories {
	mavenCentral()
}

dependencies {
    // For develop
    developmentOnly 'org.springframework.boot:spring-boot-devtools'

    // Web
    implementation 'org.springframework.boot:spring-boot-starter-web'

    // thymeleaf
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

    // Test
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

	// DB
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	runtimeOnly 'mysql:mysql-connector-java'

	// lombok
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'

	// Log4j
	implementation 'org.springframework.boot:spring-boot-starter-log4j2'

	// Validation
    implementation 'org.springframework.boot:spring-boot-starter-validation'
}

tasks.named('test') {
	useJUnitPlatform()
}

- 48행 : 스프링부트가 제공하는 유효성 검사를 사용하기 위한 의존 라이브러리 추가 설정

 

2) 폼 클래스 : /template-springboot/src/main/java/com/sb/template/forms/BoardForm.java

- 스프링 부트에서 요청 바디의 유효성 검사를 위해선, 요청 폼 클래스에서 어노테이션을 선언해주는 것으로 유효성 검사의 조건을 지정할 수 있습니다.  

package com.sb.template.forms;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

import com.sb.template.entity.Board;

import lombok.Data;

@Data
public class BoardForm {

	private Integer boardNo;

	@NotNull(message = "Please input type")
	private Integer type;

	@NotBlank(message = "Please input title")
	private String title;

	@NotBlank(message = "Please input contents")
	private String contents;

	@Pattern(regexp = "^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", message = "Unsuitable inputed ID")
	private String memberId;

	public Board toEntity() {
		Board board = new Board();

		board.setType(this.type);
		board.setTitle(this.title);
		board.setContents(this.contents);
		board.setMemberId(((this.memberId == null || (this.memberId.equals(""))) ? "" : this.memberId));

		return board;
	}
}

- 16행 : Null 허용 금지 유효성 검사 설정

- 19, 22행 : 공백 허용금지 유효성검사 설정

- 25행 : 패턴에 매칭 되는 데이터만 허용하는 유효성 검사 설정, 이메일 형식의 데이터인지를 확인하는 정규표현식을 사용하여 패턴 매칭 유효성 검사를 실시하는 설정. 

이메일과 같이 자주 사용되는 패턴의 경우 @Email어노테이션과 같은 스프링 부트가 지원해주는 기능을 사용해서 구현하면 더 효율적인 개발이 가능합니다.

 

 

3) 컨트롤러/template-springboot/src/main/java/com/sb/template/controller/BoardController.java

package com.sb.template.controller;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.sb.template.entity.Board;
import com.sb.template.enums.BoardType;
import com.sb.template.forms.BoardForm;
import com.sb.template.service.BoardService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping(path = "/board")
public class BoardController {

	@Autowired
	private BoardService boardService;

	@RequestMapping(method = RequestMethod.GET, path = "list")
	public String viewBoardList(Model model) {

		List<Board> boardList = boardService.getAllBoard();
		model.addAttribute("boardList", boardList);

		return "board/list";
	}


	@RequestMapping(method = RequestMethod.GET, path = "write")
	public String writeBoard(Model model) {

		model.addAttribute("boardTypes", BoardType.getBoardTypes());

		return "board/write";
	}


	@RequestMapping(method = RequestMethod.POST, path = "write")
	public String writeCompleteBoard(@Validated BoardForm form,
			BindingResult bindingResult, Model model) {

		if (validation(bindingResult)) {
			return "redirect:/board/list";
		}

		Integer boardNo = null;
		boardNo = boardService.createBoard(form.toEntity()).getBoardNo();

		return "redirect:/board/list";
	}


	@RequestMapping(method = RequestMethod.GET, path = "read/{boardNo}")
	public String viewBoardOne(@PathVariable(required = true) int boardNo, Model model) {

        model.addAttribute("board", boardService.getBoardOne(boardNo));

		return "board/read";
	}


	@RequestMapping(method = RequestMethod.GET, path = "update/{boardNo}")
	public String updateBoard(@PathVariable(required = true) int boardNo, Model model) {

		boardService.updateBoardForm(boardNo, model);

		return "board/update";
	}


	@RequestMapping(method = RequestMethod.POST, path = "update/{boardNo}")
	public String updateCompleteBoard(
			@PathVariable(required = true) int boardNo,
			@Validated BoardForm form,
			BindingResult bindingResult, Model model) {

		if (validation(bindingResult)) {
			return "redirect:/board/list";
		}

		boardService.updateBoard(boardNo, form.toEntity());

		model.addAttribute("message", "Update Success");

		return "redirect:/board/read/"+boardNo;
	}


	@RequestMapping(method = RequestMethod.GET, path = "delete/{boardNo}")
	public String deleteBoard(@PathVariable int boardNo, Model model) {

		model.addAttribute("board", boardService.getBoardOne(boardNo));

		return "board/delete";
	}


	@RequestMapping(method = RequestMethod.POST, path = "delete")
	public String deleteCompleteBoard(
			@RequestParam(name = "boardNo", required = true) int boardNo,
			Model model) {

		boardService.deleteBoard(boardNo);

		model.addAttribute("message", "Delete Success");
		return "redirect:/board/list";
	}


	/**
	 * If validation check result is OK, return false.
	 * In the opposite case return true.
	 *
	 * @param bindingResult
	 * @return
	 */
	private boolean validation(BindingResult bindingResult) {

		if (bindingResult.hasErrors()) {
			log.error("Validation-NG : {} ", bindingResult.getAllErrors()
					.stream()
					.map(e -> e.getDefaultMessage())
					.collect(Collectors.toList())
					);

			return true;
		} else {
			log.info("Validation-OK");
			return false;
		}
	}

}

 - 51~52행, 85~87행  : @Validated 어노테이션을 폼 클래스의 매개변수 앞에 선언해 주면, 유효성 검사가 정상 동작할 겁니다. 

 매개 변수 BindingResult는 유효성 검사의 결과를 확인할 수 있는 변수로서, 반드시 폼 클래스의 매개변수 다음에 설정해 주어야 합니다. (안 해주면 에러 발생)

- 54~56행, 89~91행 : 유효성 검사를 통과하지 못한 경우 에러 내용을 로그로 출력하고 글 목록 페이지로 리다이렉트 합니다. 

- 66, 75, 85행 : @PathVariable의 required속성 값을 true로 설정하여 빈 값을 받지 않도록 설정했습니다. 

 

4. 동작확인 

 

- 글 내용을 입력하지 않은 채로 글 작성 요청을 보낸 결과, 글 목록으로 리턴하도록 동작하고 있습니다.

 

5. 전체 소스코드

https://github.com/leeyoungseung/template-springboot/tree/feature/validation

 

GitHub - leeyoungseung/template-springboot

Contribute to leeyoungseung/template-springboot development by creating an account on GitHub.

github.com


유효성 검사에 대한 포스트는 이상으로 마치겠습니다.

다음 포스트는 예외처리에 대한 내용이 되겠습니다.

반응형

댓글