[Spring] ๐Ÿ’พ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ (@Valid, @Validated, BindingResult)

shrยท2022๋…„ 2์›” 24์ผ
0

Concept

๋ชฉ๋ก ๋ณด๊ธฐ
5/13
post-thumbnail

์œ ํšจ์„ฑ ๊ฒ€์‚ฌ


request ํ›„, ์„œ๋ฒ„ ์ธก์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”์ธ๋”ฉํ•  ๋•Œ ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•œ์ง€ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.

๐Ÿ’ก ๋ฐ”์ธ๋”ฉ (binding)

์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๊ฐ€์ง€๊ณ  ์ปค๋งจ๋“œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ž‘์—…์„ ์Šคํ”„๋ง์—์„œ๋Š” ๋ฐ”์ธ๋”ฉ(binding)ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค.


BindingResult


์Šคํ”„๋ง์ด ์ œ๊ณตํ•˜๋Š” ๊ฒ€์ฆ ์˜ค๋ฅ˜ ๋ณด๊ด€ ๊ฐ์ฒด์ด๋‹ค. ๋ฐ์ดํ„ฐ์— ์œ ํšจํ•˜์ง€ ์•Š์€ ์†์„ฑ์ด ์žˆ๋‹ค๋ฉด, ๊ทธ์— ๋Œ€ํ•œ ์˜ค๋ฅ˜ ์ •๋ณด๊ฐ€ ๋‹ด๊ธด๋‹ค. ํ•ด๋‹น ์ •๋ณด๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ์— ์ „๋‹ฌํ•ด ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋ฅผ ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌํ•ด ์ค„ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿšจ ์ปจํŠธ๋กค๋Ÿฌ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์‚ฌ์šฉํ•  ๋•Œ, ๋ฐ˜๋“œ์‹œ @Valid ๋’ค์— ์œ„์น˜ํ•ด์•ผ ํ•œ๋‹ค.


@Valid


์ž๋ฐ” ํ‘œ์ค€ ๊ฒ€์ฆ ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค. ์ปจํŠธ๋กค๋Ÿฌ์˜ ๋ฉ”์†Œ๋“œ์— ์ž‘์„ฑํ•œ๋‹ค. ์ปจํŠธ๋กค๋Ÿฌ์—์„œ๋งŒ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋‹ค๋ฅธ ๊ณ„์ธต์—์„œ์˜ ๊ฒ€์ฆ์ด ํ•„์š”ํ•  ๋•Œ๋Š” @Validated์™€ ๊ฒฐํ•ฉํ•ด์•ผ ํ•œ๋‹ค. @Valid์— ์˜ํ•œ ์˜ˆ์™ธ ํด๋ž˜์Šค๋Š” MethodArgumentNotValidException์ด๋‹ค.


@Validated


์Šคํ”„๋ง์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฒ€์ฆ ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค. @Validated๋ฅผ ํด๋ž˜์Šค๋ช… ์œ„์— ์ž‘์„ฑํ•˜๊ณ , ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•  ๋ฉ”์†Œ๋“œ์— @Valid๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค. @Validated์— ์˜ํ•œ ์˜ˆ์™ธ ํด๋ž˜์Šค๋Š” ConstraintViolationException์ด๋‹ค.


์‚ฌ์šฉ ์˜ˆ์‹œ


๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์žˆ๋‹ค.

package com.example.demo.entity;

import java.time.LocalDate;

import javax.validation.constraints.*;

import com.example.demo.entity.*;

import lombok.*;

@Data
@AllArgsConstructor
@Builder
@Accessors(chain=true)
public class Member {
	private String username;
	private String password;
	private String irum;
	private String email;
	private LocalDate birthday;
	private LocalDate joinday;
	private Boolean enabled;
	private String authority;
	private String checkcode;
	private Integer count;
	private Level levels;
	private Integer loginFailCnt;
}

์ด๋Ÿฌํ•œ ์—”ํ‹ฐํ‹ฐ์—์„œ ์•„์ด๋””(username), ๋น„๋ฐ€๋ฒˆํ˜ธ, ์ด๋ฆ„, ์ด๋ฉ”์ผ๋งŒ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ด ๋ณด์ž. ์ด ์ƒํƒœ ๊ทธ๋Œ€๋กœ ๋„˜๊ธฐ๊ฒŒ ๋˜๋ฉด, ํ•„์š”ํ•˜์ง€ ์•Š์€ ํ•„๋“œ๋“ค์€ null ๊ฐ’์œผ๋กœ ๋„˜์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค. ์ด๋•Œ ํ•„์š”ํ•œ ๊ฒƒ์ด DTO์ด๋‹ค.


DTO

package com.example.demo.dto;

import java.time.LocalDate;

import javax.validation.constraints.*;

import com.example.demo.entity.Member;

import lombok.*;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MemberDto {
	@Data
	public static class Join {
		@NotEmpty(message = "์•„์ด๋””๋Š” ํ•„์ˆ˜ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค.")
		@Pattern(regexp = "^[A-Z0-9]{8,10}$", message = "์•„์ด๋””๋Š” ๋Œ€๋ฌธ์ž๋‚˜ ์ˆซ์ž 8-10์ž์ž…๋‹ˆ๋‹ค.")
		private String username;
		@NotEmpty(message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค.")
		private String password;
		@NotEmpty(message = "์ด๋ฆ„์€ ํ•„์ˆ˜ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค.")
		private String irum;
		@NotEmpty(message = "์ด๋ฉ”์ผ์€ ํ•„์ˆ˜ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค.")
		private String email;
		// @NotEmpty(message = "์ƒ์ผ์€ ํ•„์ˆ˜ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค.")
		private LocalDate birthday;
		
		public Member toEntity() {
			return Member.builder().username(username).password(password).irum(irum).email(email).birthday(birthday).build();
		}
	}
}

Join ํด๋ž˜์Šค์˜ ๊ฐ ํ•„๋“œ ์ œ์•ฝ ์กฐ๊ฑด์— ๋”ฐ๋ผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•œ๋‹ค.


Controller

@Validated
@Controller
public class MemberController {
	@Autowired
	private MemberService service;
    
	@PreAuthorize("isAnonymous()")
	@PostMapping("/member/join")
	public String join(@Valid MemberDto.Join dto, BindingResult bindingResult, RedirectAttributes ra) {
		service.join(dto);
		ra.addFlashAttribute("msg", "๊ฐ€์ž… ํ™•์ธ ์ฝ”๋“œ๋ฅผ ์ด๋ฉ”์ผ๋กœ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฉ”์ผ์„ ํ™•์ธํ•˜์„ธ์š”");
		return "redirect:/member/check_join";
	}
  }

์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค๋ช… ์œ„์— @Validated๋ฅผ ์ž‘์„ฑํ•˜๊ณ , ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ํ•  ๋ฉ”์†Œ๋“œ์— @Valid๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค. ๋งŒ์•ฝ ๊ฒ€์ฆ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, ํ•ด๋‹น ๊ฒ€์ฆ ์˜ค๋ฅ˜๋Š” ConstraintViolationException์œผ๋กœ bindingResult์— ๋‹ด๊ธด๋‹ค.


ControllerAdvice

package com.example.demo.controller.advice;

import javax.validation.*;

import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.*;

@ControllerAdvice(basePackages = "com.example.demo.controller.mvc")
public class MyShopAdvice {
	@ExceptionHandler(ConstraintViolationException.class)
	public String constraintViolationException(ConstraintViolationException e, RedirectAttributes ra) {
		ra.addFlashAttribute("msg", e.getMessage());
		return "redirect:/system/error";
	}
}

ConstraintViolationException์€ ControllerAdvice์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋œ๋‹ค.


์ œ์•ฝ ์กฐ๊ฑด ์–ด๋…ธํ…Œ์ด์…˜


@NotNull: ํ•ด๋‹น ๊ฐ’์ด null์ด ์•„๋‹Œ์ง€ ๊ฒ€์ฆํ•จ
@NotEmpty: ํ•ด๋‹น ๊ฐ’์ด null์ด ์•„๋‹ˆ๊ณ , ๋นˆ ์ŠคํŠธ๋ง("") ์•„๋‹Œ์ง€ ๊ฒ€์ฆํ•จ(" "์€ ํ—ˆ์šฉ๋จ)
@NotBlank: ํ•ด๋‹น ๊ฐ’์ด null์ด ์•„๋‹ˆ๊ณ , ๊ณต๋ฐฑ(""๊ณผ " " ๋ชจ๋‘ ํฌํ•จ)์ด ์•„๋‹Œ์ง€ ๊ฒ€์ฆํ•จ
@AssertTrue: ํ•ด๋‹น ๊ฐ’์ด true์ธ์ง€ ๊ฒ€์ฆํ•จ
@Size: ํ•ด๋‹น ๊ฐ’์ด ์ฃผ์–ด์ง„ ๊ฐ’ ์‚ฌ์ด์— ํ•ด๋‹นํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•จ(String, Collection, Map, Array์—๋„ ์ ์šฉ ๊ฐ€๋Šฅ)
@Min: ํ•ด๋‹น ๊ฐ’์ด ์ฃผ์–ด์ง„ ๊ฐ’๋ณด๋‹ค ์ž‘์ง€ ์•Š์€์ง€ ๊ฒ€์ฆํ•จ
@Max: ํ•ด๋‹น ๊ฐ’์ด ์ฃผ์–ด์ง„ ๊ฐ’๋ณด๋‹ค ํฌ์ง€ ์•Š์€์ง€ ๊ฒ€์ฆํ•จ


์ฐธ๊ณ  ์ž๋ฃŒ


[Spring] @Valid์™€ @Validated๋ฅผ ์ด์šฉํ•œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์˜ ๋™์ž‘ ์›๋ฆฌ ๋ฐ ์‚ฌ์šฉ๋ฒ• ์˜ˆ์‹œ - (1/2)

profile
๋ชปํ•˜๋‹ค ๋ณด๋ฉด ์ž˜ํ•˜๊ฒŒ ๋˜๋Š” ๊ฑฐ์•ผ ๏ฝฅแด—๏ฝฅฬฅฬฅฬฅ

0๊ฐœ์˜ ๋Œ“๊ธ€