[Spring] 21. @RequestBody란?

[Spring] 21. @RequestBody란?

이전 글에서는 @ModelAttribute와 DTO 바인딩에 대해 살펴봤습니다. @ModelAttribute는 HTML form이나 요청 파라미터로 전달된 값을 DTO 객체에 담을 때 사용합니다.

이번 글에서는 JSON 요청 데이터를 객체로 받는 @RequestBody에 대해 정리하겠습니다. Spring에서 REST API를 만들 때 자주 사용하는 어노테이션입니다.

핵심은 간단합니다. @RequestBody는 요청 Body에 들어있는 JSON 데이터를 Java 객체로 변환해서 Controller 메서드에서 사용할 수 있게 해줍니다.


1. @RequestBody란?

@RequestBody는 HTTP 요청 Body에 담긴 데이터를 Java 객체로 받을 때 사용하는 어노테이션입니다. 주로 JSON 데이터를 받을 때 사용합니다.

예를 들어 클라이언트가 다음과 같은 JSON 데이터를 서버로 보낸다고 생각해보겠습니다.

{
  "title": "Spring 공부",
  "content": "RequestBody 정리",
  "category": "backend"
}

Controller에서는 이 JSON 데이터를 DTO로 받을 수 있습니다.

@PostMapping("/api/boards")
@ResponseBody
public String write(@RequestBody BoardDto boardDto) {
    System.out.println(boardDto.getTitle());
    System.out.println(boardDto.getContent());
    System.out.println(boardDto.getCategory());

    return "ok";
}

위 코드에서 @RequestBody는 요청 Body에 들어온 JSON 값을 BoardDto 객체로 변환합니다.

JSON 요청 Body
 → @RequestBody
 → BoardDto 객체로 변환
 → Controller 메서드에서 사용

2. 요청 Body란?

HTTP 요청은 크게 요청 URL, Header, Body 등으로 구성됩니다. 그중 Body는 서버로 보낼 데이터를 담는 영역입니다.

POST /api/boards
Content-Type: application/json

{
  "title": "Spring 공부",
  "content": "RequestBody 정리"
}

여기서 아래 JSON 부분이 요청 Body입니다.

{
  "title": "Spring 공부",
  "content": "RequestBody 정리"
}

@RequestBody는 이 Body 안에 들어있는 데이터를 읽어서 Java 객체로 바꿔줍니다.


3. JSON과 DTO 매핑

JSON의 key 이름과 DTO의 필드명이 같으면 Spring이 값을 매핑할 수 있습니다.

{
  "title": "Spring 공부",
  "content": "RequestBody 정리",
  "category": "backend"
}
public class BoardDto {

    private String title;
    private String content;
    private String category;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }
}

매핑 흐름은 다음과 같습니다.

JSON title
 → BoardDto.title

JSON content
 → BoardDto.content

JSON category
 → BoardDto.category

즉, JSON의 key와 DTO 필드명을 맞추는 것이 중요합니다.


4. @ModelAttribute와 @RequestBody 차이

@ModelAttribute@RequestBody는 둘 다 요청 데이터를 객체로 받을 수 있습니다. 하지만 받는 데이터 형태가 다릅니다.

구분 @ModelAttribute @RequestBody
주 사용 데이터 HTML form 데이터 JSON 요청 Body
데이터 예시 title=Spring&content=내용 {"title":"Spring","content":"내용"}
주 사용 방식 JSP, Thymeleaf form REST API, fetch, axios
Controller 예시 write(BoardDto boardDto) write(@RequestBody BoardDto boardDto)

처음에는 이렇게 구분하면 됩니다.

HTML form 전송
 → @ModelAttribute 또는 DTO 바인딩

JSON 전송
 → @RequestBody

5. HTML form 전송 예시

먼저 HTML form 전송을 보겠습니다. 이 방식은 보통 @ModelAttribute나 DTO 바인딩으로 받습니다.

<form action="/board/write" method="post">
    <input type="text" name="title">
    <textarea name="content"></textarea>
    <button type="submit">등록</button>
</form>
@PostMapping("/board/write")
public String write(BoardDto boardDto) {
    return "redirect:/board/list";
}

이 구조에서는 form의 name 값이 DTO 필드에 매핑됩니다.

name="title"
 → BoardDto.title

name="content"
 → BoardDto.content

이 방식은 화면 기반 게시판, 회원가입, 로그인 폼에서 자주 사용합니다.


6. JSON 전송 예시

이번에는 JSON 전송입니다. JavaScript의 fetch를 사용해서 JSON 데이터를 보낼 수 있습니다.

fetch("/api/boards", {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify({
        title: "Spring 공부",
        content: "RequestBody 정리",
        category: "backend"
    })
});

이 요청은 JSON 데이터를 요청 Body에 담아 서버로 보냅니다. Controller에서는 @RequestBody로 받을 수 있습니다.

@PostMapping("/api/boards")
@ResponseBody
public String write(@RequestBody BoardDto boardDto) {
    return "ok";
}

요청 흐름은 다음과 같습니다.

JavaScript 객체
 → JSON 문자열로 변환
 → 요청 Body에 담아 전송
 → @RequestBody가 DTO로 변환
 → Controller에서 사용

7. Content-Type이 중요한 이유

JSON 데이터를 보낼 때는 요청 Header에 Content-Type: application/json을 지정하는 것이 중요합니다.

headers: {
    "Content-Type": "application/json"
}

이 Header는 서버에게 요청 Body에 들어있는 데이터 형식이 JSON이라는 것을 알려줍니다.

Content-Type: application/json
 → 요청 Body가 JSON 형식이라는 의미

만약 JSON을 보내면서 Content-Type을 제대로 지정하지 않으면 Spring이 요청 Body를 기대한 방식으로 읽지 못할 수 있습니다.


8. @ResponseBody와 함께 보기

@RequestBody는 요청 Body를 읽는 역할입니다. 반대로 @ResponseBody는 응답 Body에 데이터를 직접 담아 반환하는 역할입니다.

@RequestBody 요청 Body의 JSON을 Java 객체로 받습니다.
@ResponseBody Java 객체나 문자열을 응답 Body로 직접 반환합니다.

예를 들어 다음 코드는 JSON 요청을 받고 문자열을 응답합니다.

@PostMapping("/api/boards")
@ResponseBody
public String write(@RequestBody BoardDto boardDto) {
    return "ok";
}

화면 이름을 반환하는 것이 아니라 응답 데이터 자체를 반환합니다.

return "ok";
 → ok라는 응답 데이터 반환

이 구조는 API를 만들 때 자주 사용합니다.


9. @RestController와 함께 사용하기

API Controller에서는 @Controller@ResponseBody를 함께 쓰는 대신 @RestController를 사용할 수 있습니다.

@RestController
@RequestMapping("/api/boards")
public class BoardApiController {

    @PostMapping
    public String write(@RequestBody BoardDto boardDto) {
        return "ok";
    }
}

@RestController는 Controller의 메서드 반환값을 View 이름으로 보지 않고, 응답 Body 데이터로 처리합니다.

@Controller + @ResponseBody
 → 응답 데이터를 직접 반환

@RestController
 → 기본적으로 응답 데이터를 직접 반환

그래서 REST API를 만들 때는 @RestController를 자주 사용합니다.


10. JSON 배열 받기

@RequestBody는 JSON 객체뿐만 아니라 JSON 배열도 받을 수 있습니다.

[
  {
    "title": "첫 번째 글",
    "content": "내용 1"
  },
  {
    "title": "두 번째 글",
    "content": "내용 2"
  }
]

Controller에서는 List로 받을 수 있습니다.

@PostMapping("/api/boards/bulk")
public String writeBulk(@RequestBody List<BoardDto> boardList) {
    System.out.println("등록 개수: " + boardList.size());
    return "ok";
}

요청 Body의 JSON 배열이 List<BoardDto>로 변환됩니다.

JSON 배열
 → @RequestBody
 → List<BoardDto>

11. JSON key와 DTO 필드명이 다르면?

JSON key와 DTO 필드명이 다르면 값이 제대로 들어오지 않을 수 있습니다.

{
  "boardTitle": "Spring 공부"
}
public class BoardDto {

    private String title;
}

JSON에서는 boardTitle로 보냈는데 DTO 필드는 title입니다. 이름이 다르기 때문에 자동 매핑이 되지 않을 수 있습니다.

가장 쉬운 방법은 JSON key와 DTO 필드명을 맞추는 것입니다.

{
  "title": "Spring 공부"
}
public class BoardDto {

    private String title;
}

특별히 이름을 다르게 써야 한다면 JSON 매핑 어노테이션을 사용할 수도 있지만, 처음에는 이름을 맞추는 방식으로 이해하는 것이 좋습니다.


12. @RequestBody를 쓰기 좋은 상황

@RequestBody는 JSON 데이터를 주고받는 API에서 자주 사용합니다.

상황 예시
프론트엔드에서 JSON 전송 React, Vue, JavaScript fetch 요청
REST API 작성 POST /api/boards
JSON 객체를 DTO로 받고 싶을 때 @RequestBody BoardDto boardDto
JSON 배열을 List로 받고 싶을 때 @RequestBody List<BoardDto>

반대로 JSP나 Thymeleaf의 일반 form 전송이라면 @ModelAttribute 또는 DTO 바인딩을 사용하는 경우가 더 많습니다.


13. 자주 하는 실수

1) form 전송을 @RequestBody로 받으려는 경우

HTML form 전송은 보통 @RequestBody가 아니라 DTO 바인딩이나 @ModelAttribute로 받습니다.

<form action="/board/write" method="post">
    <input type="text" name="title">
    <button type="submit">등록</button>
</form>
@PostMapping("/board/write")
public String write(BoardDto boardDto) {
    return "redirect:/board/list";
}

2) JSON 전송인데 Content-Type을 빼먹는 경우

JSON 데이터를 보낼 때는 Content-Type: application/json을 지정하는 것이 중요합니다.

fetch("/api/boards", {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify({
        title: "Spring"
    })
});

3) JSON.stringify를 빼먹는 경우

fetch로 JSON을 보낼 때는 JavaScript 객체를 JSON 문자열로 바꿔야 합니다.

body: JSON.stringify({
    title: "Spring"
})

JSON.stringify를 빼먹으면 서버가 기대한 JSON 형태로 받지 못할 수 있습니다.

4) JSON key와 DTO 필드명이 다른 경우

JSON key와 DTO 필드명이 다르면 값이 들어오지 않을 수 있습니다. 처음에는 이름을 맞추는 것이 가장 안전합니다.

JSON key: title
DTO 필드: title

이름을 맞추면 매핑이 쉽다.

5) @Controller에서 @ResponseBody를 빼먹는 경우

@Controller에서 문자열을 반환하면 Spring은 그 문자열을 View 이름으로 해석할 수 있습니다. API 응답 데이터를 직접 반환하려면 @ResponseBody를 붙이거나 @RestController를 사용해야 합니다.

@PostMapping("/api/boards")
@ResponseBody
public String write(@RequestBody BoardDto boardDto) {
    return "ok";
}

14. 전체 흐름 정리

JSON 요청을 @RequestBody로 받는 흐름은 다음과 같습니다.

1. 클라이언트가 JavaScript 객체를 만든다.
2. JSON.stringify로 JSON 문자열로 변환한다.
3. Content-Type을 application/json으로 지정한다.
4. 요청 Body에 JSON을 담아 서버로 보낸다.
5. Controller에서 @RequestBody로 DTO를 받는다.
6. Spring이 JSON을 DTO 객체로 변환한다.
7. Controller가 Service를 호출한다.
8. 필요한 결과를 응답한다.

이 흐름을 이해하면 프론트엔드와 백엔드가 JSON으로 데이터를 주고받는 구조를 더 쉽게 만들 수 있습니다.


정리

@RequestBody는 요청 Body에 들어있는 JSON 데이터를 Java 객체로 받을 때 사용하는 어노테이션입니다. REST API에서 JSON 요청을 DTO로 받을 때 자주 사용합니다.

@RequestBody 요청 Body의 JSON을 Java 객체로 변환합니다.
@ModelAttribute HTML form 데이터나 요청 파라미터를 객체로 받습니다.
@ResponseBody 응답 데이터를 Body에 직접 담아 반환합니다.
@RestController 기본적으로 응답 데이터를 Body로 반환하는 Controller입니다.

처음에는 다음 기준으로 기억하면 좋습니다.

HTML form 데이터
 → @ModelAttribute 또는 DTO 바인딩

JSON 요청 데이터
 → @RequestBody

응답 데이터를 직접 반환
 → @ResponseBody 또는 @RestController

연습해보기

아래 JSON 요청을 받을 DTO와 Controller 메서드를 작성해보세요.

{
  "memberId": "springUser",
  "memberPw": "1234",
  "memberName": "스프링사용자"
}

예시는 다음과 같습니다.

public class MemberJoinDto {

    private String memberId;
    private String memberPw;
    private String memberName;

    // getter, setter
}
@RestController
@RequestMapping("/api/members")
public class MemberApiController {

    @PostMapping
    public String join(@RequestBody MemberJoinDto memberJoinDto) {
        return "ok";
    }
}

다음 글에서는 응답 데이터를 직접 반환하는 @ResponseBody@RestController에 대해 정리하겠습니다.