Spring @ModelAttribute 사용법, POST 요청에서도 자동 바인딩될까?

 

Spring MVC에서 @ModelAttribute는 @PostMapping에서도 자동으로 동작합니다. Form 데이터와 JSON 요청의 차이, @RequestBody와의 사용 기준, 자동 바인딩 원리까지 예제와 함께 자세히 정리했습니다.

Spring을 처음 배우다 보면 @ModelAttribute는 GET 요청에서만 사용하는 것처럼 느껴질 수 있습니다. 특히 @PostMapping에서 DTO가 자동으로 채워지는지 궁금해하는 경우가 많습니다.

결론부터 말하면 @ModelAttribute는 GET뿐만 아니라 POST 요청에서도 자동으로 데이터를 바인딩합니다. 다만 요청 방식과 Content-Type에 따라 사용할 어노테이션이 달라집니다. 이 차이만 이해하면 @ModelAttribute와 @RequestBody를 언제 사용해야 하는지 쉽게 구분할 수 있습니다.


@ModelAttribute는 POST 요청에서도 자동으로 동작한다

예를 들어 로그인 정보를 전달하는 DTO가 있다고 가정해 보겠습니다.

public class LoginDto {

    private String id;
    private String password;

    // getter, setter
}

컨트롤러는 다음과 같이 작성할 수 있습니다.

@PostMapping("/login")
public String login(@ModelAttribute LoginDto dto) {

    System.out.println(dto.getId());
    System.out.println(dto.getPassword());

    return "ok";
}

클라이언트가 다음과 같이 Form 데이터를 전송하면

POST /login
Content-Type: application/x-www-form-urlencoded

id=hong&password=1234

Spring은 내부적으로 다음과 같이 자동으로 값을 넣어줍니다.

dto.setId("hong");
dto.setPassword("1234");

즉, 개발자가 직접 setter를 호출하지 않아도 요청 파라미터 이름과 DTO의 필드명이 일치하면 자동으로 매핑됩니다.


@ModelAttribute는 생략해도 된다

복합 객체(DTO)를 매개변수로 사용하는 경우에는 @ModelAttribute를 생략해도 동일하게 동작합니다.

@PostMapping("/login")
public String login(LoginDto dto) {

    return "ok";
}

Spring MVC는 기본적으로 복합 객체를 @ModelAttribute 방식으로 처리하기 때문입니다.

반면 String, Long, int 같은 단순 타입은 @RequestParam 방식으로 처리됩니다.


JSON 요청은 @ModelAttribute가 아니다

많은 개발자가 가장 많이 헷갈리는 부분입니다.

다음처럼 JSON을 전송하는 경우에는 @ModelAttribute가 동작하지 않습니다.

POST /login
Content-Type: application/json

{
    "id":"hong",
    "password":"1234"
}

이 경우에는 반드시 @RequestBody를 사용해야 합니다.

@PostMapping("/login")
public String login(@RequestBody LoginDto dto) {

    return "ok";
}

@RequestBody는 HTTP Body에 있는 JSON 데이터를 읽어 Jackson과 같은 HttpMessageConverter를 이용해 DTO로 변환합니다.

반면 @ModelAttribute는 Body의 JSON을 읽지 않고 요청 파라미터(Form 데이터)를 기준으로 객체를 생성합니다.


Content-Type에 따라 사용하는 어노테이션

Content-Type@ModelAttribute@RequestBody

application/x-www-form-urlencoded 사용 사용하지 않음
multipart/form-data 사용 사용하지 않음
application/json 사용하지 않음 사용

실무에서는 이 표 하나만 기억해도 대부분의 상황을 구분할 수 있습니다.


fetch()로 요청할 때 차이

JSON 전송

fetch("/login", {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify({
        id: "hong",
        password: "1234"
    })
});

컨트롤러

@PostMapping("/login")
public String login(@RequestBody LoginDto dto) {

    return "ok";
}

FormData 전송

const formData = new FormData();

formData.append("id", "hong");
formData.append("password", "1234");

fetch("/login", {
    method: "POST",
    body: formData
});

컨트롤러

@PostMapping("/login")
public String login(@ModelAttribute LoginDto dto) {

    return "ok";
}

URLSearchParams 전송

const params = new URLSearchParams();

params.append("id", "hong");
params.append("password", "1234");

fetch("/login", {
    method: "POST",
    body: params
});

이 역시 application/x-www-form-urlencoded 형식으로 전송되므로 @ModelAttribute가 자동으로 DTO를 채워줍니다.


실무에서는 어떻게 사용할까?

실무에서는 다음 기준으로 사용하는 경우가 대부분입니다.

  • HTML Form 제출 → @ModelAttribute
  • 파일 업로드(FormData) → @ModelAttribute
  • REST API(JSON 통신) → @RequestBody

특히 프론트엔드(Vue, React, Nuxt)와 API 서버를 분리해서 개발하는 경우에는 대부분 JSON을 사용하므로 @RequestBody를 사용하는 일이 더 많습니다.

반면 JSP, Thymeleaf와 같은 서버 사이드 렌더링 환경에서는 Form 전송이 많아 @ModelAttribute를 자주 사용하게 됩니다.


자주 묻는 질문(FAQ)

Q. @ModelAttribute는 POST 요청에서도 자동으로 동작하나요?

네. GET과 POST 모두 동작합니다. 단, Form 데이터나 Query Parameter를 대상으로 자동 바인딩합니다.

Q. JSON 요청인데 DTO가 비어 있습니다.

JSON 요청이라면 @ModelAttribute가 아니라 @RequestBody를 사용해야 합니다.

Q. @ModelAttribute를 생략해도 되나요?

복합 객체(DTO)라면 생략해도 동일하게 동작합니다.

Q. 파일 업로드도 @ModelAttribute를 사용하나요?

네. multipart/form-data 요청에서는 MultipartFile과 함께 @ModelAttribute를 사용하는 것이 일반적입니다.


마무리

@ModelAttribute는 GET 요청에서만 사용하는 기능이 아닙니다. POST 요청에서도 Form 데이터라면 Spring이 자동으로 DTO를 생성하고 값을 바인딩해 줍니다.

반대로 JSON 요청이라면 @RequestBody를 사용해야 하며, 두 어노테이션의 가장 큰 차이는 데이터를 어디에서 읽어오는지에 있습니다.

실무에서는 Form 데이터는 @ModelAttribute, **JSON 데이터는 @RequestBody**라는 기준만 기억해도 대부분의 상황에서 올바르게 사용할 수 있습니다.