BindingResult
BindingResult
는 Spring Framework에서 제공하는 인터페이스로, 폼 데이터 바인딩 및 검증(Validation) 결과를 처리하기 위해 사용됩니다. 주로 컨트롤러에서 요청 데이터를 도메인 객체에 매핑한 후, 바인딩 과정에서 발생한 오류나 검증 결과를 확인하고 처리하는 데 활용됩니다.
주요 역할
1. 데이터 바인딩 결과 확인
클라이언트로부터 전달된 요청 데이터를 객체에 바인딩하는 과정에서 발생한 에러(예: 타입 불일치, 필수값 누락 등)를 확인할 수 있습니다.
2. 검증(Validation) 결과 처리
Validator나 @Valid
를 사용해 검증 로직을 실행한 후 발생하는 검증 에러를 저장하고 관리합니다.
BindingResult
사용 방법
BindingResult
는 컨트롤러 메서드에서 검증 대상 객체 바로 다음에 위치해야 합니다. 이는 검증된 결과를 BindingResult
를 통해 처리하기 위함입니다.
예제: 폼 데이터 처리와 검증
1. 도메인 객체 정의
Copy public class User {
@NotEmpty(message = "이름은 필수입니다.")
private String name;
@Min(value = 18, message = "나이는 최소 18살 이상이어야 합니다.")
private int age;
// Getters and Setters
}
2. 컨트롤러에서 BindingResult
사용
Copy @Controller
@RequestMapping("/users")
public class UserController {
@PostMapping("/register")
public String registerUser(@Valid @ModelAttribute User user, BindingResult bindingResult, Model model) {
// 검증 오류 확인
if (bindingResult.hasErrors()) {
// 에러 정보를 모델에 추가
model.addAttribute("errors", bindingResult.getAllErrors());
return "userForm"; // 에러 발생 시 다시 폼 화면으로
}
// 정상 처리 로직
model.addAttribute("message", "사용자가 성공적으로 등록되었습니다.");
return "success";
}
}
3. 뷰에서 에러 출력
Copy <form action="/users/register" method="post">
<div>
<label for="name">이름:</label>
<input type="text" id="name" name="name" value="${user.name}" />
<span>${errors[0]?.defaultMessage}</span>
</div>
<div>
<label for="age">나이:</label>
<input type="text" id="age" name="age" value="${user.age}" />
<span>${errors[1]?.defaultMessage}</span>
</div>
<button type="submit">등록</button>
</form>
주요 메서드
BindingResult
는 다양한 메서드를 제공하여 검증 및 바인딩 결과를 처리할 수 있습니다.
1. 에러 확인
hasErrors()
: 바인딩 또는 검증 에러가 있는지 확인
hasFieldErrors(String field)
: 특정 필드에 에러가 있는지 확인
2. 에러 정보 조회
getAllErrors()
: 모든 에러를 리스트로 반환
getFieldErrors(String field)
: 특정 필드와 관련된 에러를 리스트로 반환
getFieldError(String field)
: 특정 필드의 첫 번째 에러 반환
3. 에러 메시지 접근
ObjectError
또는 FieldError
객체를 통해 검증 실패 시 메시지를 가져올 수 있습니다.
getDefaultMessage()
: 기본 메시지 반환
getField()
: 에러가 발생한 필드 이름 반환
예제: 특정 필드 에러 확인
Copy if (bindingResult.hasFieldErrors("age")) {
FieldError ageError = bindingResult.getFieldError("age");
System.out.println("에러 메시지: " + ageError.getDefaultMessage());
}
사용 시 주의사항
BindingResult
의 위치 : 반드시 검증 대상 객체 바로 다음에 위치해야 합니다. 그렇지 않으면 Spring이 에러를 처리하지 못합니다.
@Valid
또는 @Validated
: 검증을 활성화하려면 검증 대상 객체 앞에 해당 어노테이션을 붙여야 합니다.
커스텀 메시지 : 검증 메시지는 messages.properties
파일을 통해 커스텀할 수 있습니다.
Copy NotEmpty.user.name=사용자 이름을 입력해주세요.
Min.user.age=사용자의 나이는 최소 {0}살 이상이어야 합니다.
타임리프와 BindingResult를 활용한 검증 및 오류 처리
타임리프에서의 th:field
th:field
는 HTML 폼 필드와 객체의 필드를 바인딩합니다.
오류가 발생하면 FieldError
에 보관된 값을 출력합니다.
Copy <input type="text" th:field="*{field}" />
바인딩된 필드 값이 유효하지 않을 경우, BindingResult
에서 FieldError
객체를 사용하여 오류 메시지를 출력합니다.
errors.properties
와 application.properties
설정
errors.properties8
Copy required.user.userName=user name is required.
range.user.age=age must be between {0} and {1}.
application.properties
Copy spring.messages.basename=messages,errors
messages.properties
와 errors.properties
를 모두 사용할 수 있습니다.
BindingResult와 Validator를 활용한 검증 (User 예제)
BindingResult에서 메시지 코드 처리
rejectValue
와 reject
BindingResult
에서 오류를 추가할 때 FieldError
나 ObjectError
를 직접 생성하지 않아도 됩니다.
rejectValue
: 필드 수준의 오류를 추가합니다.
reject
: 객체 수준의 오류를 추가합니다.
메시지 코드 우선순위
필드 오류 코드 : 예) required.user.userId
예시 코드
Copy // 필드 오류 추가
bindingResult.rejectValue("userId", "required.user.userId");
// 메시지 처리 우선순위
// 1순위: required.user.userId (필드명 포함)
// 2순위: Required (일반 오류 코드)
검증 로직 분리
Validator 구현
Validator
인터페이스를 구현하여 검증 로직을 분리합니다.
Copy package spring.mvc.validation;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import spring.mvc.domain.User;
@Component
public class UserValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return User.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
User user = (User) target;
// userId 검증
if (user.getUserId() == null || user.getUserId().isEmpty()) {
errors.rejectValue("userId", "required.user.userId");
} else if (user.getUserId().length() < 5) {
errors.rejectValue("userId", "length.user.userId", "User ID must be at least 5 characters.");
}
// userName 검증
if (user.getUserName() == null || user.getUserName().isEmpty()) {
errors.rejectValue("userName", "required.user.userName");
}
}
}
컨트롤러에서 Validator 사용
@InitBinder
설정
특정 컨트롤러에서 Validator
를 적용합니다.
Copy package spring.mvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import spring.mvc.domain.User;
import spring.mvc.validation.UserValidator;
@Controller
@RequestMapping("/users")
public class UserController {
private final UserValidator userValidator;
public UserController(UserValidator userValidator) {
this.userValidator = userValidator;
}
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(userValidator);
}
@GetMapping("/form")
public String showForm(Model model) {
model.addAttribute("user", new User());
return "userForm";
}
@PostMapping("/add")
public String addUser(
@ModelAttribute @Validated User user,
BindingResult bindingResult
) {
if (bindingResult.hasErrors()) {
return "userForm";
}
return "redirect:/users/success";
}
}
글로벌 Validator 설정
글로벌 범위에서 Validator
를 적용합니다.
Copy package spring.mvc.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.Validator;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import spring.mvc.validation.UserValidator;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final UserValidator userValidator;
public WebConfig(UserValidator userValidator) {
this.userValidator = userValidator;
}
@Override
public Validator getValidator() {
return userValidator;
}
}
위 설정을 통해 모든 컨트롤러에서 @Validated
를 사용하면 UserValidator
가 자동으로 적용됩니다.
Thymeleaf와 함께 검증 오류 처리
Thymeleaf 템플릿
Copy <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>User Form</title>
</head>
<body>
<form th:action="@{/users/add}" th:object="${user}" method="post">
<div>
<label for="userId">User ID:</label>
<input type="text" id="userId" th:field="*{userId}" />
<span th:errors="*{userId}">Invalid User ID</span>
</div>
<div>
<label for="userName">User Name:</label>
<input type="text" id="userName" th:field="*{userName}" />
<span th:errors="*{userName}">Invalid User Name</span>
</div>
<div>
<label for="rank">Rank:</label>
<input type="text" id="rank" th:field="*{rank}" />
</div>
<button type="submit">Submit</button>
</form>
</body>
</html>
messages.properties
Copy required.user.userId=User ID is required.
length.user.userId=User ID must be at least 5 characters long.
required.user.userName=User Name is required.
Last updated 4 months ago