db까지 전달되기 전에 처리되는 전처리
의존성 주입
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.2.0</version>
</dependency>
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation', version: '3.2.0'
컨트롤러의 함수의 입력받는 매개변수에 @Valid 어노테이션 추가
@PostMapping("/auth/signup")
public String signup(@Valid SignupDto signupDto, BindingResult bindingResult) {
검증이 필요한 객체의 컬럼에 어노테이션 추가
@Data
public class SignupDto {
@Size(min = 2, max = 20)
@NotBlank
private String username;
@NotBlank
private String password;
@NotBlank
private String email;
@NotBlank
private String name;
SignupDto 객체에서 username, password, email, name에 대하여 추가함
db에서 확인해서 처리되는 후처리
@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity // DB에 테이블을 생성
@Table(name = "user") // user 테이블을 만들어줌
public class User {
@Id // Primary Key
@GeneratedValue(strategy = GenerationType.IDENTITY) // 번호 증가 전략이 데이터베이스를 따라감
private int id;
@Column(length = 20, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String email;
@Column(nullable = false)
private String name;
private String website; // 웹사이트
각 컬럼에 validation 요구사항을 추가
에러코드 확인
@PostMapping("/auth/signup")
public String signup(@Valid SignupDto signupDto, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
Map<String, String> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach(error -> {
errorMap.put(error.getField(), error.getDefaultMessage());
});
}
예외처리를 위한 controllerAdvice 사용
@RestController
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public String validationException(RuntimeException e) {
return e.getMessage();
}
}
위와같이 @ControllerAdvice 어노테이션을 사용하면 모든 예외를 핸들링 가능하다.
validationException함수는 모든 RuntimeException을 핸들링한다.
그러나 위와같이 사용하면 컨트롤러의 signup함수에서 발생한 에러들의 defaultMessage를 모은 errorMap을 전달할 수 없다.
RuntimeExcepttion을 상속받아서 custom한 객체 생성
public class CustomValidationException extends RuntimeException {
// 객체를 구분할 때 사용하는 값
private static final long serialVersionUID = 1L;
private Map<String, String> errorMap;
public CustomValidationException(String massege, Map<String, String> errorMap) {
super(massege);
this.errorMap = errorMap;
}
public Map<String, String> getErrorMap() {
return errorMap;
}
}
위의 코드에서 message는 클래스에서 가지고 있을 필요가 없다
왜냐하면 상위 클래스로 던지면 상위클래스가 가지고 있다.
exceptionHandler 수정
@RestController
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(CustomValidationException.class)
public Map<String, String> validationException(CustomValidationException e) {
return e.getErrorMap();
}
}
runtimeException객체를 상속받은 CustomValidationException객체를 인자로 받아서 errorMap을 리턴한다.
공통 dto 생성
errorMap뿐 아니라 message등 기타정보들로 함께 return할 수 있도록 공통 dto를 생성한다.
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CMRespDto {
private String message;
private Map<String, String> errorMap;
}
ControllerExceptionHandler 수정
@RestController
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(CustomValidationException.class)
public CMRespDto validationException(CustomValidationException e) {
return new CMRespDto(e.getMessage(), e.getErrorMap());
}
}
{
"message": "유효성 검사 실패함",
"errorMap": {
"name": "공백일 수 없습니다",
"email": "공백일 수 없습니다",
"username": "크기가 2에서 20 사이여야 합니다"
}
}
위와같이 응답을 확인할 수 있다.
응답 dto를 공통으로 사용가능하도록 수정
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CMRespDto<T> {
private int code; // 1(성공), -1(실패)
private String message;
private T data;
}
위와같이 제네릭을 사용해서 수정하게 되면 데이터의 유형에 상관없이 받을 수 있다.
ControllerExceptionHandler도 수정
@RestController
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(CustomValidationException.class)
public CMRespDto validationException(CustomValidationException e) {
return new CMRespDto(-1, e.getMessage(), e.getErrorMap());
}
}
원래페이지에서 에러페이지로 이동하지 않고 에러창만 만들어서 반환하기
public class Script {
public static String back(String message) {
StringBuffer sb = new StringBuffer();
sb.append("<script>");
sb.append("alert('").append(message).append("');");
sb.append("history.back();");
sb.append("</script>");
return sb.toString();
}
}
위와 같이 스크립트를 반환하는 클래스를 생성
ControllerExceptionHandler수정
@RestController
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(CustomValidationException.class)
public String validationException(CustomValidationException e) {
return Script.back(e.getErrorMap().toString());
}
}