요청 매핑과 요청 데이터

3줄 요약

  • Spring MVC는 URL, HTTP method, header, content type 조건으로 handler method를 선택한다.
  • 선택된 handler method의 파라미터는 ArgumentResolver가 요청 정보에서 값을 꺼내 만들어 준다.
  • 요청 데이터는 query parameter, path variable, form data, cookie, header, request body처럼 출처가 다르므로 애너테이션도 다르게 선택해야 한다.

핵심 정리

  • @RequestMapping@GetMapping, @PostMapping은 요청 조건을 handler method에 연결한다.
  • @RequestParam은 query string이나 form field 같은 요청 파라미터를 읽는다.
  • @PathVariable은 URL 경로 템플릿의 일부를 읽는다.
  • @ModelAttribute는 요청 파라미터를 객체의 프로퍼티나 생성자 인자에 바인딩한다.
  • @RequestBody는 HTTP body를 HttpMessageConverter로 읽어 객체로 변환한다.
  • form data는 Servlet request parameter 처리와 연결되므로 JSON body처럼 무조건 @RequestBody로 읽는다고 생각하면 안 된다. 일반 form은 @RequestParam이나 @ModelAttribute, multipart의 일부 JSON은 @RequestPart를 검토한다.

헷갈리는 지점

  • @RequestParam@RequestBody를 둘 다 클라이언트가 보낸 값이라고만 이해하기 쉽다. 둘 다 요청 데이터이기 때문이다.
    • 핵심은 데이터 위치가 다르다는 점이다.
    • query/form parameter는 @RequestParam 계열이고 JSON body는 @RequestBody 계열이다.
  • @PathVariable@RequestParam을 모두 문자열 값 추출로만 보기 쉽다. Controller에서는 둘 다 파라미터로 들어오기 때문이다.
    • 핵심은 URL 경로의 식별자는 @PathVariable, 필터나 옵션 값은 @RequestParam이 자연스럽다는 점이다.
    • /members/{id}의 id와 /members?page=1의 page는 의미가 다르다.
  • @ModelAttribute를 화면 MVC 전용으로만 생각하기 쉽다. 이름에 Model이 들어가기 때문이다.
    • 핵심은 요청 파라미터를 객체에 바인딩하는 기능이라는 점이다.
    • form 제출과 query parameter 기반 검색 조건 DTO에 모두 사용할 수 있다.
  • @ModelAttribute@RequestBody를 둘 다 객체 생성 도구로만 보기 쉽다. 둘 다 DTO를 만들어 주기 때문이다.
    • 핵심은 입력을 읽는 통로가 다르다는 점이다.
    • @ModelAttribute는 request parameter 기반 바인딩이고, @RequestBody는 MessageConverter 기반 body 읽기다.

확인 질문

  • JSON 요청 body를 DTO로 받고 싶다면 무엇을 쓰는가?
    • @RequestBody를 사용한다.
  • URL 경로의 일부 값을 받고 싶다면 무엇을 쓰는가?
    • @PathVariable을 사용한다.
  • 여러 요청 파라미터를 객체 하나에 묶고 싶다면 무엇을 쓰는가?
    • @ModelAttribute를 사용한다.

타입 변환과 데이터 바인딩

3줄 요약

  • HTTP 요청 값은 기본적으로 문자열이므로 Java 타입으로 변환하는 과정이 필요하다.
  • Spring은 ConversionService와 DataBinder를 통해 문자열 값을 primitive, wrapper, enum, date, 객체 프로퍼티로 변환한다.
  • 바인딩 오류와 검증 오류를 구분해야 입력 처리 흐름을 안정적으로 설계할 수 있다.

핵심 정리

  • ?age=20은 HTTP에서는 문자열이지만 Controller에서는 int age로 받을 수 있다.
  • Spring은 conversion과 binding 인프라를 통해 문자열 값을 필요한 타입으로 변환한다.
  • @ModelAttribute는 기본 생성자, setter, constructor binding 등 객체 생성/프로퍼티 바인딩 규칙과 연결된다.
  • 타입 변환 실패는 validation 실패와 다르다. 숫자로 바꿀 수 없는 문자열은 검증 이전의 binding 문제다.
  • 바인딩 결과를 직접 다뤄야 하는 화면 MVC에서는 BindingResult 위치가 중요하다.

헷갈리는 지점

  • 타입 변환 실패와 Bean Validation 실패를 같은 것으로 보기 쉽다. 둘 다 입력 오류로 보이기 때문이다.
    • 핵심은 변환 실패는 Java 타입을 만들지 못한 문제이고, validation 실패는 만들어진 값이 규칙을 어긴 문제라는 점이다.
    • “abc를 숫자로 변환 실패”와 “나이가 음수라 검증 실패”는 다른 층위다.
  • DTO에 setter가 없으면 바인딩이 무조건 안 된다고 생각하기 쉽다. 전통적인 JavaBean 바인딩 경험 때문이다.
    • 핵심은 Spring 버전과 객체 형태에 따라 constructor binding도 가능하다는 점이다.
    • 다만 팀 코드에서는 DTO 생성 규칙을 명확히 통일하는 편이 좋다.
  • 모든 변환 규칙을 Controller에서 직접 처리하려고 하기 쉽다. 입력 값이 Controller에 들어오기 때문이다.
    • 핵심은 반복되는 타입 변환은 Converter, Formatter, WebDataBinder 등 공통 인프라로 빼는 것이 좋다는 점이다.
    • Controller는 변환된 결과를 받아 요청 흐름을 제어하는 데 집중한다.

확인 질문

  • HTTP 요청 값이 Java 숫자 타입으로 들어올 수 있는 이유는 무엇인가?
    • Spring의 타입 변환 인프라가 문자열 요청 값을 대상 타입으로 변환하기 때문이다.
  • 타입 변환 실패와 검증 실패는 무엇이 다른가?
    • 변환 실패는 Java 타입 생성 자체가 실패한 것이고, 검증 실패는 변환된 값이 비즈니스/입력 규칙을 어긴 것이다.
  • 반복되는 타입 변환 규칙은 어디로 분리하는 것이 좋은가?
    • Converter, Formatter, WebDataBinder 같은 공통 변환 인프라로 분리하는 것이 좋다.

RequestBody와 MessageConverter

3줄 요약

  • @RequestBody는 HTTP body를 읽어 Java 객체로 변환한다.
  • JSON 변환은 보통 Jackson 기반 HttpMessageConverter가 담당한다.
  • Content-Type, body 구조, DTO 생성 규칙이 맞지 않으면 400 또는 415 계열 문제가 발생할 수 있다.

핵심 정리

  • @RequestBody는 query parameter나 form parameter가 아니라 request body를 읽는다.
  • JSON 요청은 Content-Type: application/json과 converter 선택이 중요하다.
  • Jackson은 JSON 필드와 DTO 필드를 매핑하며, 생성자와 setter, record 구조에 따라 역직렬화 방식이 달라질 수 있다.
  • body는 기본적으로 한 번 읽는 stream 성격이 있으므로 Filter나 Interceptor에서 무심코 읽으면 Controller에서 문제가 생길 수 있다.
  • @Valid @RequestBody 검증 실패는 기본적으로 MethodArgumentNotValidException으로 이어지고, method parameter 자체의 제약 조건 검증은 HandlerMethodValidationException으로 이어질 수 있다.
  • 요청 DTO는 API 계약이므로 Entity와 분리하는 편이 안전하다.

헷갈리는 지점

  • @RequestBody가 없는데도 JSON이 DTO에 들어올 것이라고 기대하기 쉽다. @ModelAttribute도 객체를 만들어 주기 때문이다.
    • 핵심은 JSON body와 요청 파라미터 바인딩은 다른 경로라는 점이다.
    • JSON body는 MessageConverter가 읽어야 하므로 @RequestBody가 필요하다.
  • form submit도 body에 실려 오니까 @RequestBody가 맞다고 생각하기 쉽다. HTTP body라는 단어가 같기 때문이다.
    • 핵심은 Servlet API에서 form parameter 접근은 body 파싱과 연결되어 있어 @RequestBody로 안정적으로 다시 읽기 어렵다는 점이다.
    • HTML form은 @ModelAttribute@RequestParam, multipart part 단위 데이터는 @RequestPart를 우선 검토한다.
  • Content-TypeAccept를 혼동하기 쉽다. 둘 다 HTTP header이고 JSON 문제에서 자주 등장하기 때문이다.
    • 핵심은 Content-Type은 요청 body 형식이고, Accept는 원하는 응답 형식이라는 점이다.
    • 415는 주로 요청 body media type 문제, 406은 응답 형식 협상 문제로 본다.
  • Entity를 곧바로 요청 DTO로 쓰면 편하다고 느끼기 쉽다. 필드가 비슷하기 때문이다.
    • 핵심은 API 입력 계약과 도메인 모델의 변경 이유가 다르다는 점이다.
    • 요청 DTO를 분리하면 검증, 보안, 내부 모델 변경에 강해진다.

확인 질문

  • JSON 요청 body를 Java 객체로 변환하는 주요 컴포넌트는 무엇인가?
    • HttpMessageConverter이며, JSON은 보통 Jackson 기반 converter가 담당한다.
  • Content-Type은 무엇을 나타내는가?
    • 클라이언트가 보낸 요청 body의 media type을 나타낸다.
  • 요청 DTO와 Entity를 분리하는 이유는 무엇인가?
    • 외부 API 계약과 내부 도메인 모델의 변경 이유가 다르기 때문이다.

응답 바인딩과 반환값 처리

3줄 요약

  • Controller 반환값은 ReturnValueHandler가 해석해 view rendering 또는 response body 작성 흐름으로 보낸다.
  • @ResponseBody, @RestController, ResponseEntity는 객체를 HTTP body로 변환하는 대표 방식이다.
  • 응답 DTO, status, header, content type을 명시적으로 설계해야 API가 안정적이다.

핵심 정리

  • 문자열 반환은 @Controller에서는 view name, @ResponseBody 맥락에서는 body 문자열일 수 있다.
  • ResponseEntity는 body뿐 아니라 HTTP status와 header를 함께 표현한다.
  • 객체 응답은 MessageConverter를 통해 JSON 등으로 직렬화된다.
  • 응답 DTO는 Entity 노출, 순환 참조, lazy loading 문제를 줄이는 안전장치다.
  • 에러 응답도 정상 응답처럼 구조와 status를 설계해야 클라이언트가 안정적으로 처리할 수 있다.

헷갈리는 지점

  • Controller가 객체를 반환하면 항상 JSON이라고 생각하기 쉽다. REST API 예제에 익숙하기 때문이다.
    • 핵심은 @ResponseBody 맥락이 있어야 body 변환 흐름으로 간다는 점이다.
    • 일반 @Controller에서는 반환값 종류와 설정에 따라 view 처리 흐름이 될 수 있다.
  • ResponseEntity를 항상 써야 한다고 생각하기 쉽다. status를 명시할 수 있어서 안전해 보이기 때문이다.
    • 핵심은 status와 header를 제어해야 할 때 특히 유용하다는 점이다.
    • 단순 200 응답은 DTO 반환과 @ResponseStatus 등으로도 충분할 수 있다.
  • Entity를 응답으로 바로 내보내도 된다고 느끼기 쉽다. 필드가 이미 있으므로 편하기 때문이다.
    • 핵심은 응답은 외부 계약이라는 점이다.
    • Entity 직접 노출은 내부 구조 노출, lazy loading, 순환 참조, 보안 필드 노출로 이어질 수 있다.

확인 질문

  • Controller 반환값은 누가 해석하는가?
    • HandlerAdapter 내부의 ReturnValueHandler들이 해석한다.
  • ResponseEntity는 언제 유용한가?
    • HTTP status, header, body를 함께 명시적으로 제어해야 할 때 유용하다.
  • Entity를 응답 DTO와 분리하는 이유는 무엇인가?
    • 외부 API 계약을 안정화하고 내부 모델, lazy loading, 보안 필드 노출 문제를 줄이기 위해서다.