Validation์ด๋ ์ด๋ค ๋ฐ์ดํฐ์ ๊ฐ์ด ์ ํจํ์ง, ํ๋นํ์ง ํ์ธํ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ด๋ฉ์ผ ์ฃผ์ ์์์ admin@example.com์ธ๋ฐ, ํ์ ๊ฐ์ ์ ํ ๋ ์ด๋ฉ์ผ ์์์ด ์ผ์นํ์ง ์์ผ๋ฉด ์ ํจํ์ง ์์ ์ด๋ฉ์ผ์ด๋ฏ๋ก ํ์ ๊ฐ์ ์ ๋ง์ ์ ์์ต๋๋ค.
UI์์ javascript๋ก "์ด๋ฉ์ผ ์์์ด ์ผ์นํ์ง ์๋๋ค"๋ ๊ฒ์ UX ์ธก๋ฉด์์ ์ฌ์ฉ์์๊ฒ ํธ์๋ฅผ ์ฃผ๊ธฐ ์ํจ์ ๋๋ค. ์ฌ์ฉ์๊ฐ ์๋ชป ์ ๋ ฅํ์ฌ ์คํ๊ฐ ๋ฐ์ ํ์ผ๋ ๋ค์ ํ ๋ฒ ํ์ธ์ ํ๋ผ๋ ์๋ฏธ๊ฐ ๊ฐํฉ๋๋ค. ์ฆ, UI๋จ์์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ๋ ๊ฒ์ ๋ณด์์ ์ธ ์ธก๋ฉด์์ ์๋ฌด ํจ๊ณผ๊ฐ ์๋ค๋ ๊ฒ์ ๋๋ค.
๋ณด์์ ์ธ ์ธก๋ฉด์์ ์ ํจ์ฑ ๊ฒ์ฌ๋ ์ฌ๋ฐ๋ฅด์ง ์์ ๋ฐ์ดํฐ๊ฐ ์๋ฒ๋ก ์ ์ก๋๊ฑฐ๋, DB์ ์ ์ฅ๋์ง ์๋๋ก ํ๋ ๊ฒ์ ๋๋ค.
์์กด์ฑ ์ค์
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.0.Final</version>
</dependency>
์์กด์ฑ์ ์ถ๊ฐํ๋ ๋ถ๋ถ์ ๋ช ๊ฐ ๊ฒ์ํด๋ณด๋ฉด validationa-api๋ ์ถ๊ฐํด์ฃผ๋ ๊ฒ์ ๋ณผ ์ ์๋๋ฐ, hibernate-validator์์กด์ฑ์ ์ถ๊ฐํ๋ฉด validationa-api์์กด์ฑ๋ ํจ๊ป ์ถ๊ฐ๋๋ฏ๋ก validation-api ์์กด์ ์๋ตํด๋ ๋ฉ๋๋ค.
๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ๋ฒ
๊ธฐ๋ณธ์ ์ผ๋ก ์ ํจ์ฑ ๊ฒ์ฌ๋ @Valid ๋๋ @Validated ์ด๋ ธํ ์ด์ ์ ์ด์ฉํ ์ ์์ต๋๋ค. ๋ค๋ง, ๋ ์ด๋ ธํ ์ด์ ๋ง์ผ๋ก๋ ์ ํจ์ฑ์ ๊ฒ์ฌํ ์ ์๊ณ , ํด๋์ค์ ์ฌ๋ฌ๊ฐ์ง ์ ์ฝ์กฐ๊ฑด์ ๊ดํ ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐํด์ผ @Valid, @Validated๊ฐ ๋ถ์ ๊ฐ์ฒด๋ฅผ ์ ์ฝ์กฐ๊ฑด ์ด๋ ธํ ์ด์ ๋ค์ ์ด์ฉํ์ฌ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํด์ฃผ๋ ๊ฒ์ ๋๋ค. @Valid, @Validated ์ด๋ ธํ ์ด์ ์ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ธฐ๋ฅ์ด ๊ฐ์ง๋ง, @Validated๋ ํ๋์๋ค ์ ์ฝ์กฐ๊ฑด์ ๋ํ ๊ทธ๋ฃน์ ๋ง๋ค์ด ์ ์ฉ์ํฌ ์ ์์ต๋๋ค.
๋, BindingResult ๊ฐ์ฒด๋ฅผ ํ๋ผ๋ฏธํฐ์ ๋ฃ์ด์ฃผ๋ฉด ์ ํจ์ฑ ๊ฒ์ฌ์ ์คํจํ ๋, ์๋ฌ ๋ด์ฉ์ BindingResult๊ฐ์ฒด์ ๋ฐ์ธ๋ฉํ ์ ์์ต๋๋ค. ์ฆ, BindingResult๋ฅผ ์ด์ฉํด ์ ํจ์ฑ ๊ฒ์ฌ์ ์คํจํ์ ๊ฒฝ์ฐ์ ๋ฐ๋ฅธ ๋ค์ํ ์ฒ๋ฆฌ๋ฅผ ํด์ค ์ ์๊ณ , DispatcherServlet์ด BindingResult๋ฅผ JSP์ ๋ฃ์ด์ฃผ๊ธฐ ๋๋ฌธ์ JSP์์ ์ ํจ์ฑ ๊ฒ์ฌ์ ์คํจํ์ ๊ฒฝ์ฐ๋ฅผ ์ฒ๋ฆฌํด์ค ์ ์์ต๋๋ค.
๋ง์ฝ @Valid, @Validated๋ฅผ ์ฌ์ฉ ์ค ์ ํจ์ฑ ๊ฒ์ฌ์ ์คํจํ ๋, ์ปจํธ๋กค๋ฌ ๋ฉ์๋๊ฐ BindingResult ๊ฐ์ฒด๋ฅผ ๋ฐ์ง ์๋๋ค๋ฉด ๊ฒ์ฆ์๋ฌ ๋ฐ์ ์ MethodArgumentNotValidException์ ๋ด๋ณด๋ ๋๋ค.
์ฐธ๊ณ ๋งํฌ
victolee93๋ ๋ธ๋ก๊ทธ(JSP์์ BindingResult ์ฌ์ฉํ๊ธฐ)
์ ์ฝ์กฐ๊ฑด ์ด๋ ธํ ์ด์
๊ณต์๋ฌธ์์์ ์์ฃผ ์ฌ์ฉ๋ ๊ฒ ๊ฐ์ ์ด๋ ธํ ์ด์ ๋ง ์ถ๋ ค์ ๊ฐ์ ธ์์ต๋๋ค.
@AssertFalse | ๊ฐ์ด false ์ธ์ง ํ์ธํฉ๋๋ค. |
@AssertTrue | ๊ฐ์ด true ์ธ์ง ํ์ธํฉ๋๋ค. |
@Null | ๊ฐ์ด null์ธ์ง ํ์ธํฉ๋๋ค. |
@NotNull | ๊ฐ์ด null์ด ์๋์ง ์ฒดํฌํฉ๋๋ค. |
@NotEmpty | ๊ฐ์ด null๋ ๋น๊ฒ๋ ์๋์ง ํ์ธํฉ๋๋ค. CharSequence, Collection, Map, ๋ฐฐ์ด๋ง ์ง์๋ฉ๋๋ค. |
@NotBlank | ๊ฐ์ด null์ด ์๋๊ณ , ํธ๋ฆผ ๋ ๊ธธ์ด๊ฐ 0๋ณด๋ค ํฐ์ง ํ์ธํฉ๋๋ค. @NotEmpty์์ ์ฐจ์ด์ ์ ์ด ์ ์ฝ์ CharSequence์๋ง ์ ์ฉ๋๊ณ ํธ๋ฆผ๋๋ค๋ ๊ฒ์ ๋๋ค. |
์ง์ ๋ ๋ฌธ์ ์ํ์ค๊ฐ ์ ํจํ ์ ์ ๋ฉ์ผ ์ฃผ์์ธ์ง ์ฌ๋ถ๋ฅผ ํ์ธํฉ๋๋ค. ์ ํ์ ๋งค๊ฐ ๋ณ์ regexp ๋ฐ flags๋ฅผ ์ฌ์ฉํ๋ฉด ์ ์ ๋ฉ์ผ๊ณผ ์ผ์นํด์ผํ๋ ์ถ๊ฐ ์ ๊ท ํํ์ (์ ๊ท์ ํ๋๊ทธ ํฌํจ)์ ์ง์ ํ ์ ์์ต๋๋ค. |
|
@Max(value=) | ๊ฐ์ด ์ง์ ๋ ์ต๋๊ฐ๋ณด๋ค ์๊ฑฐ๋ ๊ฐ์์ง ํ์ธํฉ๋๋ค. |
@Min(value=) | ๊ฐ์ด ์ง์ ๋ ์ต์๊ฐ๋ณด๋ค ํฌ๊ฑฐ๋ ๊ฐ์์ง ํ์ธํฉ๋๋ค. |
@Size(min=, max=) | ๊ฐ์ด min=๊ณผ max= ์ฌ์ด์ ์๋์ง ํ์ธํฉ๋๋ค.(๊ฒฝ๊ณ ํฌํจ) |
@Positive | ๊ฐ์ด ์์ ์ธ์ง ํ์ธํฉ๋๋ค. |
@PositiveOrZero | ๊ฐ์ด ์์์ด๊ฑฐ๋ 0์ธ์ง ํ์ธํฉ๋๋ค. |
@Negative | ๊ฐ์ด ์์์ธ์ง ํ์ธํฉ๋๋ค. |
@NegativeOrZero | ๊ฐ์ด ์์์ด๊ฑฐ๋ 0์ธ์ง ํ์ธํฉ๋๋ค. |
@Past | ๊ฐ์ด ๊ณผ๊ฑฐ์ผ์์ธ์ง ํ์ธํฉ๋๋ค. |
@PastOrPresent | ๊ฐ์ด ๊ณผ๊ฑฐ ๋๋ ํ์ฌ์ธ์ง ํ์ธํฉ๋๋ค. |
@Future | ๋ ์ง๊ฐ ๋ฏธ๋์ธ์ง ํ์ธํฉ๋๋ค. |
@FutureOrPresent | ๋ ์ง๊ฐ ํ์ฌ ๋๋ ๋ฏธ๋์ธ์ง ํ์ธํฉ๋๋ค. |
@Pattern(regex=, flags=) | ์ฃผ์ด์ง ํ๋๊ทธ ๋งค์น๋ฅผ ๊ณ ๋ คํ์ฌ ๊ฐ์ด ์ ๊ท์ regex์ ์ผ์นํ๋์ง ๊ฒ์ฌํฉ๋๋ค. |
@DecimalMax(value=, inclusive=) | inclusive=false์ผ๋ ์ง์ ๋ ์ต๋๊ฐ ๋ณด๋ค ์์์ง ํ์ธํฉ๋๋ค. inclusive=true ์ด๋ฉด ๊ฐ์ด ์ง์ ๋ ์ต๋๊ฐ๋ณด๋ค ์๊ฑฐ๋ ๊ฐ์์ง ํ์ธํฉ๋๋ค. ๋งค๊ฐ๋ณ์ ๊ฐ์ BigDecimal ๋ฌธ์์ด ํํ์ ๋ฐ๋ผ ์ต๋๊ฐ์ ๋ฌธ์์ด ํํ์ ๋๋ค. |
@DecimalMin(value=, inclusive=) | ๊ฐ์ด inclusive=false์ผ๋ ์ง์ ๋ ์ต์๊ฐ ๋ณด๋ค ํฐ์ง ํ์ธํฉ๋๋ค. inclusive=true ์ด๋ฉด ๊ฐ์ด ์ง์ ๋ ์ต์๊ฐ๋ณด๋ค ํฌ๋ ๊ฐ์์ง ํ์ธํฉ๋๋ค. ๋งค๊ฐ๋ณ์ ๊ฐ์ BigDecimal ๋ฌธ์์ด ํํ์ ๋ฐ๋ผ ์ต์๊ฐ์ ๋ฌธ์์ด ํํ์ ๋๋ค. |
@Digits(integer=, fraction=) | ๊ฐ์ด integer=์ fraction=์ ์ํด ์ง์ ๋ ์๋ฆฌ์์ ์ซ์์ธ์ง ํ์ธํฉ๋๋ค. |
@CreditCardNumber(ignoreNonDigitCharacters=) | ์ ์ฉ์นด๋ ๋ฒํธ์ธ์ง ํ์ธํฉ๋๋ค. (์ ์ฉ ์นด๋ ์ ํจ์ฑ์ด ์๋, ์ฌ์ฉ์ ์ค์๋ฅผ ํ์ธํ๋ ๊ฒ์ ๋ชฉํ๋ก ํฉ๋๋ค.) ignoreNonDigitCharacters๋ ์ซ์๊ฐ ์๋ ๋ฌธ์๋ฅผ ๋ฌด์ํ๋๋ก ํ์ฉํฉ๋๋ค. (๊ธฐ๋ณธ false) |
@Length(min=, max=) | ๋ฌธ์์ด์ด ์ต์๊ฐ๊ณผ ์ต๋๊ฐ ์ฌ์ด์ ์๋์ง ํ์ธํฉ๋๋ค. |
@Range(min=, max=) | ์ซ์๊ฐ ์ต์๊ฐ๊ณผ ์ต๋๊ฐ ์ฌ์ด์ ์๋์ง ํ์ธํฉ๋๋ค. |
@URL(protocol=, host=, port=, regexp=, flags=) | protocol, host, port๊ฐ ์ง์ ๋ ๊ฒฝ์ฐ ํด๋น URL ์กฐ๊ฐ์ด ์ง์ ๋ ๊ฐ๊ณผ ์ผ์นํด์ผ ํฉ๋๋ค regexp, flags๋ฅผ ์ฌ์ฉํ๋ฉด URL์ด ์ผ์นํด์ผ ํ๋ ์ถ๊ฐ ์ ๊ท์(์ ๊ท์ ํ๋๊ทธ ํฌํจ)์ ์ง์ ํ ์ ์์ต๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ์ด ์ ์ฝ ์กฐ๊ฑด์ ์ฃผ์ด์ง ๋ฌธ์์ด์ด ์ ํจํ URL์ ๋ํ๋ด๋์ง ์ฌ๋ถ๋ฅผ ํ์ธํ๊ธฐ ์ํด java.net.URL ์์ฑ์๋ฅผ ์ฌ์ฉํ์ต๋๋ค. |
@Vaild
Post์์ฒญ ์ @RequestBody๋ฅผ ํตํด ๋ฐ์ดํฐ์ ์ ํจ์ฑ์ ๊ฒ์ฌํ๋ ๊ฒฝ์ฐ๋ฅผ ์์๋ก ๋ค๊ฒ ์ต๋๋ค.
UserDTO.java
public class UserDTO {
@NotNull
private Long id;
@NotEmpty(message = "fail")
private String password;
private String name;
// Getter, Setter ์๋ต
}
DTOํด๋์ค์ ์ ์ฝ์กฐ๊ฑด ์ด๋ ธํ ์ด์ ์ ๋ถ์ฌ์ค๋๋ค. message์์ฑ์ ์ด์ฉํด์ ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋์ message๋ฅผ ์ปค์คํฐ๋ง์ด์งํ ์ ์์ต๋๋ค.
ValidController.java
@RestController
@RequestMapping("/valid")
public class ValidController {
@PostMapping
public void valid(@RequestBody @Valid UserDTO userDTO, BindingResult result) {
if(result.hasErrors()) {
result.getAllErrors()
.forEach(objectError -> {
System.err.println("code : " + objectError.getCode());
System.err.println("message : " + objectError.getDefaultMessage());
System.err.println("name : " + objectError.getObjectName());
});
}
}
}
์์น์ ์๊ด์์ด ์ ํจ์ฑ์ ๊ฒ์ฌํ ๊ฐ์ฒด์ @Vaild๋ฅผ ๋ถ์ฌ์ฃผ๋ฉด ๋ฉ๋๋ค. @RequestBody ์์ ๋ถ์ฌ๋ ๋ฉ๋๋ค.
BindingResult์์ ๋ชจ๋ ์๋ฌ์ ๋ณด๋ฅผ ์ป์ผ๋ ค๋ฉด getAllErrors()๋ฉ์๋๋ฅผ ํธ์ถํด์ผ ํฉ๋๋ค. getAllErrors()๋ ์๋ฌ์ ๋ณด๋ฅผ List<ObjectError>์ ๋ด์ Return ํ๋๋ฐ, ์์ ๊ฒฝ์ฐ์์ List๋ฅผ Stream์ผ๋ก ์๋ฌ์ ๋ณด๋ฅผ ์ฝ์์ ์ถ๋ ฅํด์ฃผ์์ต๋๋ค. ๊ธฐํ ๋ฉ์๋๋ค์ ๊ณต์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด์ฃผ์๊ธฐ ๋ฐ๋๋๋ค.
์ฐธ๊ณ ๋งํฌ
BindingResult ๊ณต์๋ฌธ์
์ฒ๋ฆฌ์์
- @Valid๊ฐ ์ ์ธ๋์ด ์๊ธฐ ๋๋ฌธ์ UserDTO ํด๋์ค์ ์ ์ฝ์กฐ๊ฑด ์ด๋ ธํ ์ด์ (@Notnull, @NotEmpty)์ ๋ฐ๋ผ ๋ฐ์ดํฐ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํฉ๋๋ค.
- ๋ชจ๋ ํ๋์ ๋ฐ์ดํฐ๊ฐ ์ ํจํ๋ค๋ฉด ๋ฌธ์ ์์ด ์์ฑ๋ ๋ก์ง์ ์งํํฉ๋๋ค.
- ๋ฐ์ดํฐ๊ฐ ์ ํจํ์ง ์๋ ํ๋๊ฐ ์์ผ๋ฉด ๊ทธ์ ๋ํ ์๋ฌ ์ ๋ณด๋ฅผ BindingResult๋ณ์์ ๋ด์ต๋๋ค.
- BindingResult์ ๊ฐ์ด ์๋ค๋ฉด ์ ํจ์ฑ ๊ฒ์ฌ์ ์คํจํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์คํจํ ๊ฒฝ์ฐ์ ์คํํ ๋ก์ง์ ๋ง๋ค์ด์ค ์ ์์ต๋๋ค.
DTO๊ฐ ๋ค๋ฅธ ๊ฐ์ฒด๋ฅผ ๊ฐ์ง๋ ๊ฒฝ์ฐ
๋ง์ฝ DTO์์ ๋ฉค๋ฒ ํ๋์ ํ์ ์ด primitive type ์ด๋ String ํด๋์ค ์ธ์ ๋ ๋ค๋ฅธ ํด๋์ค๊ฐ ์์ ๋ ํด๋น ํด๋์ค๋ฅผ Validationํ๋ ค๋ฉด, ์๋์ ๊ฐ์ด ๋ ๋ค๋ฅธ ํด๋์ค์ @Valid๋ฅผ ์ ์ฉํ๊ณ ํด๋น ํด๋์ค์์ ๋ค์ annotation์ ์ ์ฉํ๋ฉด ๋ฉ๋๋ค.
public class UserDTO {
@NotNull
private Long id;
@NotEmpty(message = "fail")
private String password;
private String name;
@Valid
private Address address;
}
public class Address {
@NotEmpty
private String address;
@NotNull
private int addressNum;
}
ํ ์คํธ ์ฝ๋
ํ ์คํธ๋ Validator๋ฅผ ์ฌ์ฉํ ๊ฐ์ฒด์ ์ ํจ์ฑ์ ๊ฒ์ฌํ๋ ํ ์คํธ์ MockMvc๋ฅผ ์ฌ์ฉํ Controller๋ก์ง์ ๊ฒ์ฆํ๋ ํ ์คํธ๊ฐ ์์ต๋๋ค.
๊ฐ์ฒด์ ์ ํจ์ฑ ๊ฒ์ฌ (Validator ์ฌ์ฉ)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {WebMvcContextConfiguration.class, ApplicationConfig.class})
public class ValidTest {
private static Validator validator;
private static ValidatorFactory factory;
@BeforeClass
public static void init() {
factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@Test
public void validFailTest() throws Exception {
UserDTO user = new UserDTO();
user.setName("name");
Set<ConstraintViolation<UserDTO>> set = validator.validate(user);
for(ConstraintViolation<UserDTO> v : set)
System.out.println(v.getMessage());
}
}
๊ฐ์ฒด์ @Vaild๋ฅผ ํ ์คํธํ๊ณ ์ถ์ ๋ ์ฌ์ฉํฉ๋๋ค. ValidatorFactory์ getValidator()๋ก Validator๊ฐ์ฒด๋ฅผ ๋ฐ์ ์ฌ์ฉํด์ผํฉ๋๋ค. ValidatorFactory, Validator๋ @BeforClass๋ฅผ ์ด์ฉํ์ฌ ํ ์คํธ ํด๋์ค๊ฐ ์คํ๋๊ธฐ ์ ์ ์ด๊ธฐํ ํด์ฃผ์ด์ผ ํฉ๋๋ค.
Validator์ validate()์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํ ๊ฐ์ฒด๋ฅผ ๋ฃ์ด์ฃผ์์ ๋, ์ ํจ์ฑ ๊ฒ์ฌ์ ์คํจํ๊ฒ ๋๋ฉด, Set<ConstraintViolation<๊ฒ์ฌํ ๊ฐ์ฒด>>์ ์๋ฌ์ ๋ณด๋ฅผ ๋ด์ Returnํ๊ฒ ๋๊ณ , ConstraintViolation<๊ฒ์ฌํ ๊ฐ์ฒด>๋ฅผ ์ด์ฉํด ์๋ฌ์ ๋ณด๋ฅผ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
์ฐธ๊ณ ๋งํฌ
ConstraintViolation<T> ๊ณต์๋ฌธ์
Controller๋ก์ง ๊ฒ์ฆ (MockMvc ์ฌ์ฉ)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {WebMvcContextConfiguration.class, ApplicationConfig.class})
public class ValidTest {
@Autowired
public ValidController validController;
private MockMvc mockMvc;
@Before
public void createController() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(validController).build();
}
@Test
public void validFailTest() throws Exception {
UserDTO user = new UserDTO();
user.setName("name");
ObjectMapper mapper = new ObjectMapper();
this.mockMvc.perform(post("/valid")
.characterEncoding("UTF-8")
.content(mapper.writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
Http์ ๊ฐ์ด Controller๋ก์ง์ ๊ฒ์ฆํ๊ณ ์ถ์ ๋ ์ฌ์ฉํฉ๋๋ค. Controller๋ฅผ ์ฃผ์ ๋ฐ๊ณ mockMvc๋ฅผ ํด๋น Controller๋ก build()ํด์ค๋๋ค.
ValidController์์ Post์์ฒญ ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํ๊ธฐ ๋๋ฌธ์ Json์ ์ ์กํด ์ฃผ์ด์ผ ํ๋๋ฐ, ObjectMapper์ writeValueAsString()์ ๊ฐ์ฒด๋ฅผ ๋ฃ์ผ๋ฉด ๊ฐ์ฒด์ ํ๋์ ํ๋์ ๊ฐ์ Json์ผ๋ก ๋ณํํด์ฃผ๊ธฐ ๋๋ฌธ์ ๊ฐ๋จํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ฃผ์ํ ์
- Json์ ์ ์กํ ๋ body = <no character encoding set>์ฒ๋ผ body๋ฅผ ์ธ์ํ์ง ๋ชปํ๊ฑฐ๋ ํ๊ธ์ด ๊นจ์ง๋ ๊ฒฝ์ฐ, MockMvc๋ฅผ build()ํ ๋, Filter๋ฅผ ๋ฃ์ด์ฃผ์ด์ผ ํฉ๋๋ค.
- mockMvc = MockMvcBuilders.standaloneSetup(reservationInfoApiController).addFilters(new CharacterEncodingFilter("UTF-8",true)).build();
- perform(post("/valid")์์ post()๋ MockMvcRequestBuilders.post()์
๋๋ค. ์ฌ๊ธฐ์๋ ๊ฐ๋จํ๊ฒ ์ฌ์ฉํ๊ธฐ ์ํด import static์ ์ฌ์ฉํ์์ต๋๋ค.
- import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
- javax.validation.ValidationException: HV000183: Unable to initialize 'javax.el.ExpressionFactory'.์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ์๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์กด์ฑ์ ์ถ๊ฐํฉ๋๋ค.
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
์ฐธ๊ณ ๋งํฌ
MockMvc๋ฅผ ์ด์ฉํ WebAPI ํ
์คํธ
@Validated
@Validated ์ด๋ ธํ ์ด์ ์ ๊ธฐ๋ณธ์ ์ผ๋ก @Valid์ ๊ธฐ๋ฅ์ด ๊ฐ์ง๋ง, ํ๋์๋ค ์ ์ฝ์กฐ๊ฑด์ ๋ํ ๊ทธ๋ฃน์ ๋ง๋ค์ด ์ ์ฉ์ํฌ ์ ์์ต๋๋ค.
@Valid๋ ์ ์ฝ์กฐ๊ฑด์ ๋ฌ์๋์ ์์ฑ์ ๋ํด ์ ๋ถ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ๊ฒ ๋๋๋ฐ, ๋ง์ฝ ์ ์ฝ์กฐ๊ฑด ์ค ์ํ๋ ์์ฑ๋ง ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ๊ณ ์ถ์ ๊ฒฝ์ฐ์ ์ฌ์ฉํ๋ ๊ฒ์ด @Validated ์ ๋๋ค.
์ํ๋ ์์ฑ๋ค๋ง ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ๋ ค๋ฉด ๋จผ์ ๊ทธ๋ฃนํ์ ํด๋์์ผ ํ๋๋ฐ, ํ๋์ ๋ํ ์ ์ฝ์กฐ๊ฑด ๊ทธ๋ฃนํ์ ๋ฉ์๋ ์ ์ธ์ด ์๋ ๋ง์ปค ์ธํฐํ์ด์ค๋ฅผ ์ด์ฉํฉ๋๋ค.
ValidationGroups.java
public class ValidationGroups {
public interface group1 {}
public interface group2 {}
}
ValidationGroupsํด๋์ค์ ๋ฉ์๋ ์ ์ธ์ด ์๋ ๋ง์ปค ์ธํฐํ์ด์ค group1, group2๋ฅผ ๋ง๋ญ๋๋ค. ํด๋น ๊ทธ๋ฃน์ ์ํ๋ ํ๋๋ฅผ ๊ทธ๋ฃนํํ ์ ์์ต๋๋ค.
UserDTO.java
public class UserDTO {
@NotNull(groups = {ValidationGroups.group1.class})
private Long id;
@NotEmpty(message = "fail", groups = {ValidationGroups.group2.class})
private String password;
private String name;
// Getter,Setter ์๋ต
}
๊ธฐ์กด์ UserDTO์ ํ๋๋ฅผ groups์์ฑ์ ์ด์ฉํด ๊ทธ๋ฃนํํฉ๋๋ค.
ValidController.java
@RestController
@RequestMapping("/valid")
public class ValidController {
@PostMapping
public void valid(@RequestBody @Validated(ValidationGroups.group1.class) UserDTO userDTO, BindingResult result) {
if(result.hasErrors()) {
result.getAllErrors()
.forEach(objectError -> {
System.err.println("code : " + objectError.getCode());
System.err.println("message : " + objectError.getDefaultMessage());
System.err.println("name : " + objectError.getObjectName());
});
}
}
}
@Validated๋ฅผ ์ ์ธํ๊ณ ๊ดํธ๋ฅผ ์ด์ด ์ํ๋ ๊ทธ๋ฃน์ ๋ฃ์ด์ฃผ๋ฉด ํด๋น ๊ทธ๋ฃน์ ์ํด์๋ ํ๋๋ง ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํ๊ฒ ๋ฉ๋๋ค.(ํ ์คํธ ์ฝ๋๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.) @Valid์ ๋ง์ฐฌ๊ฐ์ง๋ก ์์น๋ ์๊ด์์ต๋๋ค.
์ฃผ์ํ ์
- @Validated์ ๊ทธ๋ฃน์ ๋ฃ์ด์ฃผ์ง ์์ผ๋ฉด Controller์์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ์ง ์์ต๋๋ค.
@PathVariable, @RequestParam์์์ ์ ํจ์ฑ ๊ฒ์ฌ
@PathVariable, @RequestParam์์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค.
MethodValidationPostProcessor Bean ๋ฑ๋ก
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
๋จผ์ @PathVariable, @RequestParam์์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ๋ ค๋ฉด MethodValidationPostProcessor๋ฅผ Bean์ผ๋ก ๋ฑ๋กํ์ฌ์ผ ํฉ๋๋ค.
ValidController.java
@RestController
@RequestMapping("/valid")
@Validated
public class ValidController {
@GetMapping("/{id}")
public String parameterValid(@PathVariable @Email String id) {
return id;
}
}
Controllerํด๋์ค์ @Validated๋ฅผ ๋ถ์ฌ์ฃผ๊ณ , ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํ @PathVariable, @RequestParam์ ์ ์ฝ์กฐ๊ฑด ์ด๋ ธํ ์ด์ ์ ๋ถ์ฌ์ฃผ๋ฉด ๋ฉ๋๋ค.
ํ ์คํธ ์ฝ๋
@Test
public void parameterValidTest() throws Exception {
this.mockMvc.perform(get("/valid/hello")
.characterEncoding("UTF-8")
.contentType(MediaType.APPLICATION_JSON))
.andDo(print());
}
MockMvc๋ฐฉ์์ผ๋ก ๊ฐ๋จํ๊ฒ ํ ์คํธํด๋ณด๋ฉด, ConstraintViolationException ์์ธ๋ฅผ ๋ฐ์์ํค๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
์ฃผ์ํ ์
- Controller ํด๋์ค์ @Validated๋ฅผ ๋ถ์ฌ์ฃผ๊ฒ ๋๋ฉด, BindingResult์ ์๋ฌ์ ๋ณด๋ฅผ ๋ฃ์ด ์ฌ์ฉํ๋ ๋ฉ์๋์์๋ Exception์ ๋ฐ์์ํค๊ธฐ ๋๋ฌธ์ ์ฃผ์ํ์ฌ์ผ ํฉ๋๋ค.
- ๊ตฌ๊ธ๋งํด๋ณด๋ @PathVariable๊ณผ BindingResult๋ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ค๊ณ ํฉ๋๋ค.
- ์ด๋ฐ ๊ฒฝ์ฐ์๋ @ExceptionHandler๋ฅผ ํตํด ์์ธ๋ฅผ ์ ์ดํด์ฃผ์ด์ผ ํฉ๋๋ค.
ConstraintViolationException๊ณผ MethodArgumentNotValidException์ ์ฐจ์ด์
@Vaild๋ฅผ ์ด์ฉํ๋ค ๋ฐ์ํ Exception์ MethodArgumentNotValidException์ ๋๋ค. ๊ทธ๋ฐ๋ฐ ๊ฐ์ ์ ์ฝ์กฐ๊ฑด์ ์ฒ๋ฆฌํ๋๋ฐ ์ @PathVariable์์ ์ ์ฝ์กฐ๊ฑด ์ด๋ ธํ ์ด์ (@Email)์ ์ด์ฉํ๋ฉด ์ ConstraintViolationException์ด ๋ฐ์ํ ๊น์? ๊ณต์ ๋ฌธ์๋ฅผ ๋ณด๋ฉด ๊ฐ Exception์ ๋ํ ์ค๋ช ์ ์๋์ ๊ฐ์ต๋๋ค.
- ConstraintViolationException : ์ ์ฝ์กฐ๊ฑด ์๋ฐ์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ณ ํฉ๋๋ค.
- MethodArgumentNotValidException : @Valid ์ฃผ์์ด ๋ฌ๋ฆฐ ์ธ์์ ๋ํ ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ์คํจํ๋ฉด ์์ธ๊ฐ ๋ฐ์ํฉ๋๋ค. 5.3๋ถํฐ BindException์ ํ์ฅํฉ๋๋ค.
์ ํจ์ฑ ๊ฒ์ฌ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ ์ํฉ์์ @Valid๋ฅผ ์ฌ์ฉํ์ง ์์์ผ๋ฉด ConstraintViolationException, ์ฌ์ฉํ๋ค๋ฉด MethodArgumentNotValidException๊ฐ ๋ฐ์ํ๋ ๊ฒ ์ ๋๋ค.
ํ์ธ ๊ฒฐ๊ณผ, @Validated๋ ๋ง์ฐฌ๊ฐ์ง๋ก MethodArgumentNotValidException์ด ๋ฐ์ํฉ๋๋ค.
Custom Constraint Annotation
์ ์ฝ์กฐ๊ฑด ์ด๋ ธํ ์ด์ ์ผ๋ก ๋ชจ๋ ์ ์ฝ์กฐ๊ฑด์ ์ค๋ช ํ ์ ์๋ ๊ฒ์ด ์๋๊ธฐ ๋๋ฌธ์ ํ์ํ ์ ์ฝ์กฐ๊ฑด์ ์ง์ ์ปค์คํฐ๋ง์ด์งํ ์ ์์ต๋๋ค.
์์๋ฅผ ์ํด UserDTO์ nameํ๋์ Splin์ ์ ์ธํ ๋ค๋ฅธ ์ด๋ฆ์ ์ฌ ์ ์๋ ์ ์ฝ์กฐ๊ฑด ์ด๋ ธํ ์ด์ ์ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค.
NameConstraint.java(Custom Constraint annotation)
@Documented
@Constraint(validatedBy = NameConstraintValidator.class)
@Retention(RUNTIME)
@Target({ TYPE, FIELD, PARAMETER })
public @interface NameConstraint {
String message() default "Not Exist Name";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@interface๋ฅผ ์ฌ์ฉํด Custom annotation์ ๋ง๋ค ์ ์์ต๋๋ค. ์ฆ, @NameConstraint๋ผ๊ณ ์ฌ์ฉํ ์ ์๋ ๊ฒ์ ๋๋ค.
@interface์์ ๋ถ์ ์ด๋ ธํ ์ด์ ์ ๋ฉํ๋ฐ์ดํฐ ์ด๋ ธํ ์ด์ ์ด๋ผ๊ณ ํฉ๋๋ค.
Metadata Annotation
๋ฉํ๋ฐ์ดํฐ๋ ๋ฐ์ดํฐ๋ฅผ ์ํ ๋ฐ์ดํฐ์ ๋๋ค. ์ฆ, ์ด๋ค ๋ฐ์ดํฐ์ ๊ตฌ์กฐํ๋ ์ ๋ณด๋ฅผ ๋ถ์, ๋ถ๋ฅํ๊ณ ๋ถ๊ฐ์ ์ ๋ณด๋ฅผ ์ถ๊ฐํ๊ธฐ ์ํด ๊ทธ ๋ฐ์ดํฐ ๋ค์ ํจ๊ป ๋ฐ๋ผ๊ฐ๋ ์ ๋ณด๋ฅผ ๋งํฉ๋๋ค. ์ฌ๊ธฐ์์ ์ฌ์ฉ๋ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๊ฐ๋จํ๊ฒ ์ค๋ช ํ์๋ฉด ์๋์ ๊ฐ์ต๋๋ค.
- @Documented : ํด๋น ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ ํด๋์ค๊ฐ javadoc์ ๊ฐ์ด ๋ฌธ์ํ ๋ ๋, ํด๋น ์ด๋ ธํ ์ด์ ์ด ์ ์ฉ๋์์์ ๋ช ์ํ๋๋ก ํฉ๋๋ค.
- @Constraint(validatedBy = Class<T>(์ฌ๊ธฐ์๋ NameConstraintValidator.class)) : ์ ํจ์ฑ ๊ฒ์ฌ์ ์ฌ์ฉ๋ Validator๋ฅผ ์ค์ ํฉ๋๋ค.
- @Retention(RUNTIME) : ์ด๋
ธํ
์ด์
์ ๋ผ์ดํ ์ฌ์ดํด์ ์ค์ ํฉ๋๋ค.
- RetentionPolicy.SOURCE : ์์ค ์ฝ๋(.java)๊น์ง ๋จ์์์ต๋๋ค.
- RetentionPolicy.CLASS : ํด๋์ค ํ์ผ(.class)๊น์ง ๋จ์์์ต๋๋ค.(=๋ฐ์ดํธ ์ฝ๋)
- class ํ์ผ๋ง ์กด์ฌํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฐ์ ๊ฒฝ์ฐ์๋ ํ์ ์ฒด์ปค, IDE ๋ถ๊ฐ๊ธฐ๋ฅ ๋ฑ์ ์ฌ์ฉํ ์ ์์ผ๋ ค๋ฉด CLASS ์ ์ฑ ์ด ํ์
- RetentionPolicy.RUNTIME : ๋ฐํ์๊น์ง ๋จ์์์ต๋๋ค.(=์ฌ์ค์ ์ ์ฌ๋ผ์ง)
- ์ฌ๊ธฐ์์๋ import static์ผ๋ก RetentionPolicy๋ฅผ ์ ์ธํ์ต๋๋ค.
- @Target({ TYPE, FIELD, PARAMETER }) : ์ด๋
ธํ
์ด์
์ด ์์ฑ๋ ์ ์๋ ์์น๋ฅผ ์ง์ ํฉ๋๋ค. (enum์ผ๋ก ๋ ElementType์ ํ๋)
- ์ฌ๊ธฐ์์๋ import static์ผ๋ก ElementType์ ์ ์ธํ์ต๋๋ค.
Constraints Annotation์ ๋ง๋ค๊ธฐ ์ํด์๋ 3๊ฐ์ง ์์ฑ
- message : ์ ์ฝ ์กฐ๊ฑด์ ๋ง์กฑํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ ๋ฐํํ๋ ๊ธฐ๋ณธ ์๋ฌ ๋ฉ์์ง์ ๋๋ค.
- groups : ์ ์ฝ ์กฐ๊ฑด ๊ทธ๋ฃน์ ์ง์ ํ๋ ๊ฒ์ผ๋ก ์ถํ์ ๊ฒ์ฌ ์์๋ฅผ ์ง์ ํ๋ ๊ฒ๊ณผ ์ฐ๊ด์ด ์์ต๋๋ค. ๊ธฐ๋ณธ์ผ๋ก Class<?> ๋ฐฐ์ด๋ก ์ ์ํด์ผ ํฉ๋๋ค.
- payload : client์์ payload ๊ฐ์ฒด๋ฅผ ์ํด ์ฐ์ ๋๋ค.
payload : ์ ์ก๋๋ ๋ฐ์ดํฐ๋ฅผ ์๋ฏธํฉ๋๋ค. ๋ฐ์ดํฐ๋ฅผ ์ ์กํ ๋, ํค๋์ ๋ฉํ๋ฐ์ดํฐ, ์๋ฌ ์ฒดํฌ ๋นํธ ๋ฑ๊ณผ ๊ฐ์ ๋ค์ํ ์์๋ค์ ํจ๊ป ๋ณด๋ด ๋ฐ์ดํฐ ์ ์ก์ ํจ์จ๊ณผ ์์ ์ฑ์ ๋ํ๊ฒ ๋ฉ๋๋ค. ์ด ๋, ๋ณด๋ด๊ณ ์ ํ๋ ๋ฐ์ดํฐ ์์ฒด๋ฅผ payload๋ผ๊ณ ํฉ๋๋ค.
NameConstraintValidator.java(Validator)
public class NameConstraintValidator implements ConstraintValidator<NameConstraint, String> {
public static final Set<String> names = new HashSet<>(Arrays.asList("Splin"));
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && names.contains(value);
}
}
@NameConstraint๋ฅผ ์ฒ๋ฆฌํด์ฃผ๋ ค๋ฉด ๋ฐ๋์ ConstraintValidator๋ฅผ ๊ตฌํํด์ฃผ์ด์ผ ํฉ๋๋ค.
ConstraintValidator<NameConstraint, String>๋ฅผ ๋ณด๋ฉด, NameConstraint์์ String ๊ฐ์ฒด์ ๋ํ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํ ๊ฒ์์ ์ค์ ํ๊ณ , isValid ๋ฉ์๋์์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํฉ๋๋ค.
์ด๋ ๊ฒ Validator๊น์ง ๋ง๋ค์ด ์ฃผ๊ฒ ๋๋ฉด, ์๋์ ๊ฐ์ด UserDTO์์ ์ฌ์ฉํ ์ ์๊ณ , Splin์ด ์๋ ๋ค๋ฅธ ๊ฐ์ ๋ฃ์ด์ฃผ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค.
public class UserDTO {
@NotNull
private Long id;
@NotEmpty(message = "fail")
private String password;
@NameConstraint
private String name;
@Valid
private Address address;
}
์ฐธ๊ณ : https://bbbicb.tistory.com/43
https://woowacourse.github.io/javable/post/2020-09-20-validation-in-spring-boot/
https://jeong-pro.tistory.com/203
https://victorydntmd.tistory.com/179
https://otrodevym.tistory.com/entry/Spring-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%82%AC
https://m.blog.naver.com/varkiry05/222058714706
https://velog.io/@kwj1270/%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98#documented
'Back-End > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring] MapStruct์ ์ฌ์ฉ๋ฒ ๋ฐ ModelMapper์์ ๋น๊ต (0) | 2023.04.18 |
---|
๋๊ธ