이 글은 Velog에서 이전한 글입니다. Velog 원문 보기
✏️ 검증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에 대해서 알아봤다. 이제 게시판에 적용을 해보자.
게시판에 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"
- field에 검증 오류가 포함되서 온다면 class를 "field-error"로 변경한다.
검증 오류가 넘어온다면 th:errors="*{title}"로 인해 검증 오류 메시지가 넘어오고 출력된다.
이때 error.properties를 이용해서 검증 오류 메시지를 지정해줄 수 있다.
Bean Validation - 에러 코드
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
'Project > 나의 작은 프로젝트' 카테고리의 다른 글
| 💡 게시판 - 메시지, 국제화 (0) | 2026.07.21 |
|---|---|
| 💡 게시판 - 파일 다운로드 (1) | 2026.07.20 |
| 💡 게시판 - 파일 업로드 3(개선) (0) | 2026.07.20 |
| 💡 게시판 - 파일 업로드 2 (0) | 2026.07.20 |
| 💡 게시판 - 파일 업로드 (0) | 2026.07.20 |