โ๏ธ ๊ฒ์ฆ1 - Validation์์ ๊ฒ์ฆ ๊ธฐ๋ฅ์ ์๊ฐํ๋ค.(์์ธ์ง ๋ชจ๋ฅด๊ฒ ์ง๋ง ๊ธ์ด ๋ ๋ผ๊ฐ๋ค.. ์ถํ์ ๋ค์ ์์ฑํด์ผํ ๊ฒ ๊ฐ๋ค.)
๊ฒ์ฆ ๊ธฐ๋ฅ์ ์ง๋ ๊ธ์์ ์๊ฐํ ๊ฒ์ฒ๋ผ ๋งค๋ฒ ์ฝ๋๋ก ์์ฑํ๋ ๊ฒ์ ์๋นํ ๋ฒ๊ฒ๋กญ๋ค. ํนํ ํน์ ํ๋์ ๋ํ ๊ฒ์ฆ ๋ก์ง์ ๋๋ถ๋ถ ๋น ๊ฐ์ธ์ง ์๋์ง, ํน์ ํฌ๊ธฐ๋ฅผ ๋๋์ง ์๋์ง์ ๊ฐ์ด ๋งค์ฐ ์ผ๋ฐ์ ์ธ ๋ก์ง์ด๋ค.
@Entity
@Data
public class Board extends TimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@NotBlank(message = "๊ณต๋ฐฑx", groups = {SaveCheck.class, UpdateCheck.class})
@Size(min=2, max=40, groups = {SaveCheck.class, UpdateCheck.class})
private String title;
private String content;
@NotBlank(groups = {SaveCheck.class})
@Size(max=10, groups = {SaveCheck.class})
private String author;
private String filename; //์์ฉ์๊ฐ ์
๋ก๋ํ ํ์ผ ์ด๋ฆ
//...
}
์ด๋ฐ ๊ฒ์ฆ ๋ก์ง์ ๋ชจ๋ ํ๋ก์ ํธ์ ์ ์ฉํ ์ ์๊ฒ ๊ณตํตํํ๊ณ , ํ์คํ ํ ๊ฒ์ด ๋ฐ๋ก Bean Validation์ด๋ค.
Bean Validation์ ์ ํ์ฉํ๋ฉด, ์ ๋
ธํ
์ด์
ํ๋๋ก ๊ฒ์ฆ ๋ก์ง์ ๋งค์ฐ ํธ๋ฆฌํ๊ฒ ์ ์ฉํ ์ ์๋ค.
์์กด๊ด๊ณ ์ถ๊ฐ
build.gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'
Bean Validation์ ์ฌ์ฉํ๋ ค๋ฉด ์์ ์์กด๊ด๊ณ๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค.
ํ ์คํธ ์ฝ๋
public class BeanValidationTest {
@Test
void beanValidation() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Board board = new Board();
board.setTitle(" "); //๊ณต๋ฐฑ
board.setAuthor(" ");
board.setContent("ํ
์คํธ");
Set<ConstraintViolation<Board>> violations = validator.validate(board);
for (ConstraintViolation<Board> violation : violations) {
System.out.println("violation = " + violation);
System.out.println("violation.getMessage() = " + violation.getMessage());
}
}
}
์คํ ๊ฒฐ๊ณผ
violation = ConstraintViolationImpl{interpolatedMessage='๊ณต๋ฐฑ์ผ ์ ์์ต๋๋ค', propertyPath=title, rootBeanClass=class com.studyweb.webboard.service.domain.Board, messageTemplate='{javax.validation.constraints.NotBlank.message}'} violation.getMessage() = ๊ณต๋ฐฑ์ผ ์ ์์ต๋๋ค violation = ConstraintViolationImpl{interpolatedMessage='๊ณต๋ฐฑ์ผ ์ ์์ต๋๋ค', propertyPath=author, rootBeanClass=class com.studyweb.webboard.service.domain.Board, messageTemplate='{javax.validation.constraints.NotBlank.message}'} violation.getMessage() = ๊ณต๋ฐฑ์ผ ์ ์์ต๋๋ค
์ถ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด, ๊ฒ์ฆ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฐ์ฒด, ํ๋, ๋ฉ์์ง ์ ๋ณด ๋ฑ ๋ค์ํ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์๋ค.
์ฌ๊ธฐ๊น์ง Bean Validation์ ๋ํด์ ์์๋ดค๋ค. ์ด์ ๊ฒ์ํ์ ์ ์ฉ์ ํด๋ณด์.
Board
package com.studyweb.webboard.service.domain.board;
import com.studyweb.webboard.service.time.TimeEntity;
import lombok.Data;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Entity
@Data
public class Board extends TimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@NotBlank(message = "๊ณต๋ฐฑx", groups = {SaveCheck.class, UpdateCheck.class})
@Size(min=2, max=40, groups = {SaveCheck.class, UpdateCheck.class})
private String title;
private String content;
@NotBlank(groups = {SaveCheck.class})
@Size(max=10, groups = {SaveCheck.class})
private String author;
private String filename; //์์ฉ์๊ฐ ์
๋ก๋ํ ํ์ผ ์ด๋ฆ
private String filepath; // DB์ ์ ์ฅ๋๋ ํ์ผ ์ด๋ฆ(UUID ์ถ๊ฐ)
public void update(String title, String content) {
this.title = title;
this.content = content;
}
//๊ธฐ๋ณธ ์์ฑ์ ์ถ๊ฐ!
public Board() {
}
}
Entity ํด๋์ค์ธ Board ํด๋์ค์ด๋ค. title์ @Notblank
๋ฅผ ์ ์ฉํด์ ๊ณต๋ฐฑ์ด ์
๋ ฅ๋์ง ์๋๋ก ํ๋ค. ์ด๋, message๋ ๊ฒ์ฆ ์ค๋ฅ ๋ฐ์์ ๋ํ๋ด์ค ๋ฉ์์ง๋ฅผ ์ ์ด์ฃผ๋ ๊ฒ์ด๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก author์๋ Bean Validation์ ์ ์ฉํ๋ค.
groups์ ๋ํด์๋ ๋ค์์ ์๊ฐํ๊ฒ ๋ค.
BoardController
@PostMapping("/board/write")
public String boardWrite(@Validated(SaveCheck.class) @ModelAttribute Board board, BindingResult bindingResult, Model model, MultipartFile file) throws Exception {
if (bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "boardWrite";
}
//์ฑ๊ณต ๋ก์ง
boardService.save(board, file);
Integer id = board.getId();
model.addAttribute("message", "๊ธ ์์ฑ์ด ์๋ฃ๋์์ต๋๋ค.");
model.addAttribute("searchUrl", "/board/post/" + id);
return "message";
}
BoardController ์ค ๊ฒ์๊ธ ์์ฑ ๋ฉ์๋์ด๋ค.
Bean Validation์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ @ModelAttribute๊ณผ ํจ๊ป @Validated๋ฅผ ์์ฑํด์ผํ๋ค.
@Valid
,@Validated
๋ ์ ์ฌํ ๊ธฐ๋ฅ์ ํ๊ธฐ์ ๋ ์ค ํ๋๋ง ์ฌ์ฉํ๋ฉด ๋๋ค. ๊ทธ๋ฌ๋ @Validated๊ฐ ๋ค์์ ์ฌ์ฉํ groups๋ฅผ ์ง์ํ๊ธฐ์ @Validated๋ฅผ ์ฌ์ฉํ๋ค.
boardWrite
<div class="form-group">
<label for="title" th:text="#{label.title}">์ ๋ชฉ</label>
<input type="text" id="title" th:field="*{title}" th:errorclass="field-error" placeholder="์ ๋ชฉ์ ์
๋ ฅํ์ธ์.">
<div class="field-error" th:errors="*{title}">
๊ฒ์๊ธ ์ ๋ชฉ ์ค๋ฅ
</div>
</div>
๊ฒ์๊ธ ์์ฑํผ ์ค ์ ๋ชฉ ๋ถ๋ถ๋ง ๊ฐ์ ธ์๋ค.
th:errorclass="field-error"
๊ฒ์ฆ ์ค๋ฅ๊ฐ ๋์ด์จ๋ค๋ฉด th:errors="*{title}"๋ก ์ธํด ๊ฒ์ฆ ์ค๋ฅ ๋ฉ์์ง๊ฐ ๋์ด์ค๊ณ ์ถ๋ ฅ๋๋ค.
์ด๋ error.properties
๋ฅผ ์ด์ฉํด์ ๊ฒ์ฆ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ง์ ํด์ค ์ ์๋ค.
Bean Validation์ด ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ๋ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ข ๋ ์์ธํ ๋ณ๊ฒฝํ๊ณ ์ถ์ผ๋ฉด ์ด๋ป๊ฒ ํ๋ฉด ๋ ๊น?
Bean Validation์ ์ ์ฉํ๊ณ bindResult์ ๋ฑ๋ก๋ ๊ฒ์ฆ ์ค๋ฅ ์ฝ๋๋ฅผ ๋ณด์. ์ค๋ฅ ์ฝ๋๊ฐ ์ ๋
ธํ
์ด์
์ด๋ฆ์ผ๋ก ๋ฑ๋ก๋๋ค. ๋ง์น typeMismatch์ ์ ์ฌํ๋ค.
์๋๋ NotBlank
๋ผ๋ ์ค๋ฅ ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก MessageCodesResolver๋ฅผ ํตํด ๋ค์ํ ๋ฉ์์ง ์ฝ๋๊ฐ ์์๋๋ก ์์ฑ๋๊ฒ์ด๋ค.
[NotBlank.board.title, NotBlank.title,
NotBlank.java.lang.String, NotBlank];
์๋๋ ๋ด๊ฐ ์์ฑํ ๊ฒ์ฆ ์ค๋ฅ ๋ฉ์์ง์ด๋ค.
#์ถ๊ฐ
typeMismatch.java.lang.String=๋ฌธ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.
typeMismatch=ํ์
์ค๋ฅ์
๋๋ค.
#Bean Validation ์ถ๊ฐ
NotBlank.board.title=๊ฒ์๊ธ์ ์ ๋ชฉ์ ์ ์ด์ฃผ์ธ์.
NotBlank.board.author=์์ฑ์ ์ด๋ฆ์ ์ ์ด์ฃผ์ธ์.
NotBlank={0} ๊ณต๋ฐฑX
Size.board.title=๊ฒ์๊ธ์ ์ ๋ชฉ์ 2~40๊ธ์๋ง ๊ฐ๋ฅํฉ๋๋ค.
Size.board.author=์์ฑ์ ์ด๋ฆ์ 10์ ์ดํ๋ง ๊ฐ๋ฅํฉ๋๋ค.
Size = {0} ๊ธ์์ ์ ํ
์ด๋ ๋ฉ์์ง์ ์ฐ์ ์์๋ฅผ ์ค์ ํ ์ ์๋๋ฐ.
์์์ NotBlank ๋ณด๋ค NotBlank.board.title๊ฐ ์ฐ์ ์์๊ฐ ๋๋ค. ๋ ๊ตฌ์ฒด์ ์ผ์๋ก ์ฐ์ ์์๊ฐ ๋๋ค.
์ด์ ํ ๋ฒ ์คํํด์ ํ์ธํด๋ณด์
๊ฒ์๊ธ ์์ฑ ํ์ด์ง์์ ์ ๋ชฉ๊ณผ ์์ฑ์๋ฅผ ๊ณต๋ฐฑ์ผ๋ก ํ๊ณ '๊ฒ์๊ธ ์์ฑ' ๋ฒํผ์ ๋๋ ๋ค. 'error.properties'์ ์ ์ฅํ ์ค๋ฅ ๋ฉ์์ง๊ฐ ๋นจ๊ฐ ๊ธ์จ๋ก ๋ํ๋ ๊ฒ์ ์ ์ ์๋ค.
๋ ์์ธํ ์ฝ๋๋ ๊นํ๋ธ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์!
Github: https://github.com/pp8817/ToyProjectBoard
์ถ์ฒ
(๊ฐ์)์คํ๋ง MVC 2ํธ - ๋ฐฑ์๋ ์น ๊ฐ๋ฐ ํ์ฉ ๊ธฐ์