스프링 부트 : 3.2.0
여태 SSAFY에서 진행했던 프로젝트에는 프론트에서만 유효성 검사를 진행했다. (?)
그 당시에는 '값만 제대로 넘겨주면 상관없지 않나? 귀찮다' 이 생각이었는데
만약에 값이 수정되거나 잘못된 경로로 들어오게 된다면? 서버에서 문제가 발생하게 된다.
이걸 막아주기 위해서 서버에서도 유효성 검사를 필히 진행해야한다.
우선 유효성 검사를 진행할 부분은 총 3가지로 판단했다.
Entity
Entity는 기존에 제약조건을 걸어두었던 상태다.
그러나 제약 조건은 테이블을 만들 때 해당 조건에 따라 테이블이 생성되는 것일 뿐. 만약에 조건에 부합하지 않는 값이 들어오면 DB에서 에러를 뱉어내게 된다.
Entity에도 유효성 검사를 진행했던 이유는 빌더를 통해 객체를 생성하는 데 이때 유효성검사를 진행해놓지 않으면 잘못된 값이 들어온 상태로 객체가 생성 -> DB에서 에러를 발생
이 과정이 불필요하다고 생각이 들었다. 그래서 애플리케이션 수준에서 데이터 무결성을 보장하도록 했다.
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String type;
@Column(unique = true, nullable = false)
@Email(message = "이메일 형식이 아닙니다.")
@NotBlank(message = "이메일은 항상 입력돼야 합니다.")
private String email;
@Column(unique = true, nullable = false, length = 20)
@NotBlank(message = "닉네임은 항상 입력돼야 합니다.")
private String nickname;
@CreatedDate
@Column(nullable = false)
private LocalDateTime createdDate;
@LastModifiedDate
@Column(nullable = false)
private LocalDateTime updatedDate;
@Column(length = 100, nullable = true)
@Size(max = 100, message = "상태메시지는 100자를 초과할 수 없습니다.")
private String statusMessage;
@Column(nullable = false)
private String profileImage;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<InviteTeamList> inviteTeamList = new ArrayList<>();
@Column(nullable = false)
private Boolean isOnline = false;
@RequestParam, @PathVariabled
uri로 값을 넘길 때 유효성 검사를 진행해주어야 한다.
그래야 DB까지 이상한 값이 들어가지 않고 컨트롤러에 진입하기 전에 쳐낼 수 있다.
해당 어노테이션에서 유효성 검사를 진행할때는 @Validated 를 사용한다.
컨트롤러 맨 상단에 해당 어노테이션을 붙여주고 @RequestParam, @PathVariabled로 값을 받는 곳에 유효성 검사 어노테이션을 달아주면 된다.
아래 코드는 유저의 닉네임을 변경하는 api이다.
// 유저 닉네임 변경
@Operation(summary = "유저 닉네임 변경 API", description = "파라미터로 넣은 nickname을 받아서 닉네임 변경 진행합니다. 최대 10글자 가능합니다.")
@PatchMapping("/user/nickname")
public ResponseEntity<ApiResponse<?>> updateUserNickname(
@CookieValue(name = "accessToken", required = false) String accessToken,
@RequestParam
@Size(max = 10, message = "닉네임은 최대 10자까지 가능합니다.")
@NotBlank(message = "닉네임을 입력해주세요.")
@Pattern(regexp = "^[가-힣A-Za-z]+$", message = "닉네임은 한글, 영어만 가능합니다.")
String nickname){
UserNicknameUpdateResponseDTO result = userService.updateUserNickname(accessToken, nickname);
return ResponseEntity.status(200).body(ApiResponse.createSuccess(result, "유저 닉네임 변경 성공"));
}
Size, NotBlank, Pattern을 사용해서 유효성 검사를 진행하고 있다.
만약에 여기서 예외가 발생하면 어떻게 될까?
@Validated는 ConstraintViolationException을 발생시키게 된다. 해당 예외처리를 잡아서 내가 넣은 메시지를 출력시켜주면 된다.
// Validated 유효성 검사 실패
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ApiResponse<?>> handleConstraintViolationException(ConstraintViolationException e){
log.error("유효성 검사 실패 예외 발생");
String errorMessage = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(", "));
return ResponseEntity.status(400).body(ApiResponse.createClientError(errorMessage));
}
나는 위와 같이 글로벌익셉션핸들러에 넣었으며 만약에 실제 잘못된 값이 들어오는 경우
제대로 에러 메시지를 내뱉는 걸 확인할 수 있다.
@ResponseBody
컨트롤러에서는 @ResponseBody 를 통해서 DTO를 받는다.
이것도 마찬가지로 유효성 검사를 진행하고 넘겨줘야 하는데, 얘는 @Valid를 사용한다.
아래 코드는 유저의 상태메시지를 변경하는 api다.
// 유저 상태메시지 변경
@Operation(summary = "유저 상태메시지 변경 API", description = "유저의 상태메시지를 변경합니다.")
@PatchMapping("/user/status-message")
public ResponseEntity<ApiResponse<?>> changeStatusMessage(
@CookieValue(name = "accessToken", required = false) String accessToken,
@RequestBody @Valid StatusMessageRequestDTO dto){
userService.updateUserStatusMessage(accessToken, dto.getMessage());
return ResponseEntity.status(200).body(ApiResponse.createSuccessNoContent("유저 상태메시지 변경 성공"));
}
dto 에 @Valid를 통해 유효성검사를 하겠다고 선언을 해주고 DTO 클래스에 유효성 검사 어노테이션을 넣으면 된다.
@Size(max = 100, message = "상태메시지는 100자를 초과할 수 없습니다.")
private String message;
null이 가능해서 size만 체크를 진행해주었다.
@Valid는 @Validated와는 다르게 예외 클래스가 다르다.
MethodArgumentNotValidException이 발생하게 된다.
동일하게 해당 예외를 글로벌 핸들러에서 잡아주면 된다.
// Valid 유효성 검사 실패
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<?>> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
log.error("유효성 검사 실패 예외 발생");
String errorMessage = e.getBindingResult().getAllErrors().stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.joining(", "));
return ResponseEntity.status(400).body(ApiResponse.createClientError(errorMessage));
}
dto도 유효성 검사를 제대로 진행하는 것을 확인할 수 있다.
유효성 검사에 대해 찾아보다가 김영한 강사님의 의견을 확인할 수 있었다.
엔티티, DTO 유효성 검사에 대해 질문 드립니다. - 인프런
엔티티, DTO를 둘 다 유효성 검사를 하나요?만약 엔티티도 유효성 검사를 할 떄 Bean validation을 사용하시나요? - 질문 & 답변 | 인프런
www.inflearn.com
요약하자면 원칙적으로 둘다 체크하는 것이 맞지만, 실용적으로 DTO에만 진행한다.
Entity에도 진행하면 너무 중복 체크가 많아지고 코드가 더러워진다.
이것도 실무적인 관점에서 보면 너무 맞는 말이라고 생각한다.
그러나 우리는 배우는 사람 아닌가? 학생이다. 일단 모두 유효성 검사를 진행하자. 나중에 실무에 가서 불필요하면 제거를 하면 되니깐
만약에 글에 잘못된 내용이 있다면 언제든지 지적 바랍니다.
'프로젝트 > 씈크럼 프로젝트' 카테고리의 다른 글
쿼리 메서드 사용으로 인한 성능 개선 (0) | 2024.02.09 |
---|---|
조작된 토큰으로 SignatureException 발생 예외 처리 (0) | 2024.02.02 |
Swagger를 배포한 상태로 사용해보기 (0) | 2024.02.01 |
Reflection을 이용한 테스트 코드 작성 (1) | 2024.01.29 |