[데브코스] Spring Boot 인증·인가(Auth) (9강) - 글 작성 시 username 파라미터로 작성자 지정 + ConstraintViolationException 처리

zuno·2026년 1월 7일

이번 9강에서는 글 작성 API에서 작성자(user1 고정) 방식 대신,
클라이언트가 username을 전달하면 그 값으로 작성자를 찾도록 변경했다.

그리고 @NotBlank, @Size 같은 파라미터 검증이 실패할 때 발생하는 예외인
ConstraintViolationExceptionGlobalExceptionHandler에서 응답 처리하도록 추가했다.


✅ 9강 작업 요약

1) 첫 번째 커밋: 글 작성 시 username 파라미터 입력받기

  • 기존: 컨트롤러에서 작성자를 user1로 고정
  • 변경: username을 요청 파라미터로 받아서 작성자를 조회

컨트롤러 변경 포인트

@RestController
@Validated
@RequestMapping("/api/v1/posts")
public class ApiV1PostController {

    @PostMapping
    public RsData<PostDto> write(
        @Valid @RequestBody PostWriteReqBody reqBody,
        @NotBlank @Size(min = 2, max = 30) String username
    ) {
        Member actor = memberService.findByUsername(username).get();
        Post post = postService.write(actor, reqBody.title(), reqBody.content());
        ...
    }
}
  • usernameRequest Parameter로 전달받는다.
  • @NotBlank, @Size입력 검증을 걸었다.
  • @Validated를 컨트롤러 클래스에 붙여서 파라미터 검증이 실제로 동작하게 했다.

2) 두 번째 커밋: ConstraintViolationException 예외 응답 처리 추가

파라미터 검증(@NotBlank, @Size) 실패 시 발생하는 예외를 잡아서
일관된 응답으로 내려주도록 GlobalExceptionHandler에 추가했다.

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<RsData<Void>> handle(ConstraintViolationException ex) {
    String message = ex.getConstraintViolations()
        .stream()
        .map(violation -> {
            String field = violation.getPropertyPath().toString().split("\\.")[2];
            String[] messageTemplateBits = violation.getMessageTemplate().split("\\.");
            String code = messageTemplateBits[messageTemplateBits.length - 2];
            String _message = violation.getMessage();

            return "%s-%s-%s".formatted(field, code, _message);
        })
        .sorted(Comparator.comparing(String::toString))
        .collect(Collectors.joining("\n"));

    return new ResponseEntity<>(
        new RsData<>("400-1", message, null),
        BAD_REQUEST
    );
}

❓ 9강에서 헷갈렸던 포인트 정리 (Q&A)

Q1) username@NotBlank, @Size 붙이려면 @Validated가 꼭 필요해?

거의 “필요하다”가 맞다.

  • @Valid는 주로 @RequestBody(DTO) 검증에서 동작한다.
  • @NotBlank, @Size 같은 어노테이션을 메서드 파라미터(@RequestParam 등) 에 붙이면
    스프링이 “메서드 파라미터 검증”을 수행해야 하는데,
    이 기능이 활성화되려면 클래스(또는 메서드)에 @Validated가 있어야 한다.

즉,

  • @Valid @RequestBody DTO ✅ (Validated 없어도 동작하는 경우가 많음)
  • @RequestParam String username@NotBlank @Size ✅ → 보통 @Validated 필요

Q2) 스프링부트에서 URL로 들어오는 값은 어떻게 받는 게 정석이야?

URL 예시:

POST {{baseURL}}/api/v1/posts?username=user3

이건 “쿼리 스트링(query string)”이고, 스프링에서는 보통 @RequestParam으로 받는다.

@PostMapping
public RsData<PostDto> write(
    @RequestParam String username
) { ... }

근데 너 코드에서는 @RequestParam을 안 붙였는데도 받는 이유?

  • 스프링 MVC가 “단순 타입(String, int 등) 파라미터”는 상황에 따라
    @RequestParam처럼 바인딩해주는 경우가 있다.
  • 하지만 명확성을 위해 보통은 아래처럼 쓰는 걸 많이 선호한다.

✅ 추천 형태(명확하게):

public RsData<PostDto> write(
    @Valid @RequestBody PostWriteReqBody reqBody,
    @RequestParam @NotBlank @Size(min = 2, max = 30) String username
) { ... }

Q3) “URL 값을 바디에 넣으면” 어디로 받는 거야?

예: 요청 바디(JSON)에 username을 넣는 경우

{
  "title": "제목",
  "content": "내용",
  "username": "user3"
}

이건 @RequestBody DTO로 받는다.

public record PostWriteReqBody(
    @NotBlank String title,
    @NotBlank String content,
    @NotBlank @Size(min = 2, max = 30) String username
) {}

그리고 컨트롤러는:

public RsData<PostDto> write(@Valid @RequestBody PostWriteReqBody reqBody) {
    String username = reqBody.username();
}

즉 정리하면:

  • URL(QueryString) → @RequestParam
  • JSON Body → @RequestBody DTO

Q4) @Valid가 실패하면 어떤 예외가 터져?

1) @Valid @RequestBody DTO 검증 실패

  • 보통 MethodArgumentNotValidException 발생
  • 그래서 GlobalExceptionHandler에서 아래를 처리하는 패턴이 많다.
@ExceptionHandler(MethodArgumentNotValidException.class)
...

2) @NotBlank @Size 같은 “파라미터 검증” 실패

  • 보통 ConstraintViolationException 발생
  • 그래서 이번 강의에서 이걸 추가로 처리했다.
@ExceptionHandler(ConstraintViolationException.class)
...

📌 정리

  • 글 작성 API에서 username을 파라미터로 받아 작성자를 결정하도록 변경
  • 파라미터 검증을 걸기 위해 @NotBlank, @Size 적용
  • 파라미터 검증 실패 시 발생하는 ConstraintViolationException을 전역 예외 처리로 응답 통일
  • Body 검증(@Valid DTO)과 Param 검증(@Validated + constraints)은 예외 타입이 다를 수 있음

0개의 댓글