[SpringBoot] 게시판 유효성 검사, 예외처리 기능 구현하기[7]

Euiyeon Park·2025년 2월 17일
post-thumbnail

✨ 구현을 통한 학습 목표

  1. ✅ 유효성 검사
  2. ✅ 예외 처리

✨ 요구사항

1. 게시글 작성/수정 기능

  • 게시글 제목, 내용은 필수 포함
  • 제목은 1글자 이상 15글자 이하
  • 내용은 1글자 이상 1000글자 이하
  • 제목은 공백으로만 이루어질 수는 없다.

2. 특정 게시글 조회 기능

  • 게시글의 id로 특정 게시글을 조회했을 때,
    존재하지 않는 게시글일 경우 에러 메세지로 응답

3. 특정 게시글 삭제 기능

  • 게시글의 id로 특정 게시글을 삭제하기 위해 조회했을 때,
    존재하지 않는 게시글일 경우 에러 메세지로 응답

4. 게시글 검색 기능

  • 검색 키워드는 공백을 제외한 1글자 이상이어야 한다.

✨ 검증 어노테이션 - Validation

  • Spring에서 입력 값을 검증할 때, jakarta.validation 패키지에서 제공하는
    다양한 어노테이션을 사용할 수 있다.

1. 공통 검증

  • @Valid : DTO 내부 필드의 유효성 검사 수행
    • 적용 대상 : DTO (@RequestBody)
  • @Validated : @RequestParam, PathVariable 등 단일 값 검증
    • 적용 대상 : Controller 클래스

2. Null 및 빈 값 검증

  • @NotNull : null ❌,
  • @NotEmpty : null 또는 ""
  • @NotBlank : null 또는 "" 또는 " "- 공백을 허용하지 않음

💡 @NotBlank@NotEmpty보다 더 엄격하다.

3. 문자열 검증

  • @Size(min, max) : 문자열 길이 제한
  • @Pattern(regexp) : 정규식으로 검증

💡 @Pattern을 이용하면 특정 패턴(이메일, 전화번호) 검증이 가능하다.

4. 숫자 검증

  • @Min(value) : 최소값 제한 - int, long에만 적용
  • @Max(value) : 최대값 제한
  • @Positive : 양수만 허용 - 모든 숫자 타입 지원
  • @PostiveOrZero : 0 또는 양수 허용
  • @Negative : 음수만 허용
  • @NegativeOrZero : 0 또는 음수 허용
  • @Digits(integer, fraction) : 정수, 소수 자리수 제한

✨ 유효성 검사 구현

게시글 수정 기능의 유효성 검사는 게시글 작성과 동일하므로 생략

📂 PostCreateRequest.java - 게시글 작성 DTO

  • @NotBlank, @Size(min, max) 검증 어노테이션 적용
  • @NotBlank는 공백을 허용하지 않으므로, 요구사항을 만족하기 위해 적용
@Builder
public record PostCreateRequest(

        @NotBlank(message = "제목은 필수값입니다.")
        @Size(min = 1, max = 15, message = "제목은 1글자 이상 15자 이하로 작성해주세요.")
        String title,

        @NotBlank(message = "내용은 필수값입니다.")
        @Size(min = 1, max = 1000, message = "내용은 1글자 이상 1000글자 이하로 작성해주세요.")
        String content
) {
}

📂 PostController.java

  • DTO 클래스에 검증 어노테이션을 추가하고, 컨트롤러에서 @Valid를 적용
  • @Valid를 사용하면 DTO 내부 필드의 유효성 검사를 수행한다.

💡 @RequestBody가 JSON 데이터를 PostCreateRequest객체로 변환한 후, 해당 객체에 대한 유효성 검사 수행

    @PostMapping("/post")
    public ResponseEntity<PostResponse> savePost(@RequestBody @Valid PostCreateRequest request){
        return postService.savePost(request);
    }

💥 Issue : 유효성 검사

요구사항 중 게시글 검색 기능에 대해
"검색 키워드는 공백을 제외한 1글자 이상이어야 한다"는 어떻게 검사해야될지 난감했다.
왜냐 ! ! ! 나는 지금까지 유효성 검사를 DTO 검증 어노테이션 적용 + 컨트롤러에 @Valid 했기 때문 ! !
그래서 이 부분에서 다시 한번 검색 키워드 기반 검색 기능의 리팩토링 필요성을 절실히 느꼈다 ..

쨋든 구현은 해야되니까, 얼레벌레 검색해서
컨트롤러 단에서 @Validated@NotBlank 사용해 검색 키워드 유효성 검사를 구현했다.

📂 PostController.java

  • 클래스 레벨에 @Validated 어노테이션을 적용하고, 메서드 레벨에서 @NotBlank를 적용했다.
 @Validated      // 검색 키워드 유효성 검사를 위해 추가
public class PostController {

    private final PostService postService;
    
    ... 생략

    // 게시글 검색 - 검색키워드가 포함된 게시글 검색
    @GetMapping("/post/search")
    public PagedModel<PostResponse> getPostByKeyword(@RequestParam @NotBlank(message = "검색 키워드는 공백 제외 1글자 이상 입력해주세요.") String keyword,
                                                     @RequestParam(name = "page", defaultValue = "1") int page,
                                                     @RequestParam(name = "size", defaultValue = "15") int size){
        if(size > 100){ size = 100; }
        Pageable pageable = PageRequest.of(page-1, size, Sort.by("createdAt").descending());
        return postService.getPostByKeyword(keyword, pageable);
    }
    
    ... 생략
} 

@Validated

  • @Validated는 Spring에서 유효성 검사를 활성화하는 어노테이션으로,
    @RequestParam, @PathVariable 등의 개별 요청 값(단일 값)을 검증할 때 사용한다.
  • @Validated컨트롤러 클래스에 붙여야 개별 요청 파라미터의 유효성 검사가 동작한다.

💡@Valid@RequestBody(DTO)에서만 작동한다.

@Validated와 @NotBlank를 사용하지 않으면?

  • @RequestParam으로 받은 keyword를 컨트롤러에서 trim(), isEmpty()와 같은 메서드를 사용해
    검증 할 수도 있다. (하지만 간zi가 안난다)
  • 예를 들어
    @GetMapping("/post/search")
    public PagedModel<PostResponse> getPostByKeyword(@RequestParam String keyword,
                                                     @RequestParam(name = "page", defaultValue = "1") int page,
                                                     @RequestParam(name = "size", defaultValue = "15") int size){
        
        if(keyword.trim().isEmpty()){
        	throw new Exception("검색 키워드는 공백 제외 1글자 이상 입력해주세요.")      
        }
        
        ... 생략
        return postService.getPostByKeyword(keyword, pageable);
    }

쨋든 .. 결론은 검색 기능 리팩토링 해야 한다.


✨ 예외 처리 구현

예외 처리 구현은 비교적 수월했는데, 그 이유는
특정 게시물을 조회할 때나 삭제할 때나 둘 다 엔티티를 조회해오는 과정에서 발생하는 문제이기 때문에
레포지토리에서 예외 처리를 하면 된다.

💡JpaRepository에서 findById()의 반환타입이 Optinal<>임을 기억하자

📂 PostRepository.java

  • 기본 조회 메서드 findOne()를 default 메서드로 정의해,
    해당 id의 엔티티(게시글)가 존재하지 않는 경우 예외를 던지도록 처리한다,
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
    default Post findOne(Long id){
        return findById(id)
                .orElseThrow(() -> new IllegalArgumentException("게시글이 존재하지 않습니다!"));
    }
    
    ... 생략
}

💥 Issue : 예외 처리

과연 내가 한게 예외 처리가 맞나.. ? 라는 생각이 들었다.
요구사항은 "존재하지 않는 게시글일 경우 에러 메세지로 응답"인데,
내가 한건 그냥 예외를 발생시키는 코드다 ... ㅎ ㅎ ㅎ ㅎ ㅎ ㅎㅎㅎ ;;;;; ;;

예외 처리란 뭘까 ..?

  • Spring에서 예외 처리를 구현한다는 것은
    애플리케이션에서 발생할 수 있는 예외를 적절하게 감지하고,
    일관된 방식으로 처리하여 사용자에게 의미 있는 응답을 제공하는 것
    을 의미한다.
  • 즉, 예외가 발생했을 때 어떻게 응답할 것인지 정하는 것이 예외 처리 기능의 핵심이다.

@ControllerAdvice, @ExceptionHandler, @ResponseStatus

왜 안썼니 .. . .?
넌 알고 있어 의연.. 할 수 있어 유 캔 두 잇, 아이 캔 두 잇 ..

📂 Example.java

  • 여기에 ErrorResponse DTO까지 정의하면 금상첨화다..
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(EntityNotFoundException.class)
    public Map<String, String> handleEntityNotFoundException(EntityNotFoundException ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", ex.getMessage());
        return error;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public Map<String, String> handleIllegalArgumentException(IllegalArgumentException ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", ex.getMessage());
        return error;
    }
}

결론

리팩토링 열심히 하자🫠

  • PR 야무지게 써놨넹 ㅎ ㅎ ㅎ ㅎㅎㅎ ㅎㅎ ㅎ;
  • 예외 처리 구현한거 아니고 그냥 예외 발생이었음을 .. 🙏
  • 하는 김에 커스텀 예외 만들어야긋다
profile
"개발자는 해결사이자 발견자이다✨" - Michael C. Feathers

0개의 댓글