사용자의 전달값을 핸들러의 매개변수로 매핑할 때 사용되는 @RequestParam과 @ModelAttribute에 대해 평소 모호하게 느껴졌던 부분을 정확하게 이해하기 위해 이번 글을 작성하게 되었다.
@RequestParam
@RequestParam 어노테이션은 사용자가 요청 시 전달하는 값을 Handler(Controller)의 매개변수로 1:1 매핑할 때 사용하는 어노테이션이다. HTTP 요청 파라미터를 받아오기 위해 사용되고, 따라서 Body를 직접 조회하지 않는다.
@Controller
public class TestController {
@GetMapping("/")
public String getTestPage(@RequestParam("name") String name) {
System.out.println("이름 : " + name);
return "test";
}
}
예를 들어 사용자가 /?name=test 로 요청한다면, 위 핸들러의 매개변수인 name에 "test"가 매핑된다.
@ModelAttribute
@ModelAttribute는 사용자가 요청시 전달하는 값을 오브젝트 형태로 매핑해 주는 어노테이션이다.
메서드레벨, 메서드의 파라미터 두 군데에 적용이 가능하다.
먼저 메서드 레벨에 @ModelAttribute를 적용했을 시, Model에 저장되는 과정을 살펴보자.
- key : @ModelAttribute()
- value : 반환결과
private @ModelAttribute("day") char getDay(MyDate mydate) {
return getDay(mydate.getYear(), mydate.getMonth(), mydate.getDay());
}
- key : day / value : getDay() 메서드 반환결과가 바로 Model에 저장된다.
메서드 레벨에 적용한 @ModelAttribute 어노테이션으로 인해 이 메서드는 자동으로 호출이 된다.
알아서 호출하고 작업결과를 Model에 알아서 저장하기 때문에 임의로 메서드를 호출하고, 작업 결과를 Model에 저장하지 않아도 된다.
또한 @ModelAttribute는 생략 가능하다.
이번에는 매개변수에 @ModelAttribute를 적용한 경우를 살펴보자.
@RequestMapping("/getDay")
public String main(@ModelAttribute MyDate date, Model model) throws IOException {
...
}
위처럼 매개변수에 적용하면, Model에 자동으로 저장이 된다.
기존 처리 방식과 @ModelAttribute를 사용했을 때의 경우를 비교해 보면 아래와 같다.
기존 처리 방식
@RequestMapping("/getDay")
public String main(MyDate date, Model model) throws IOException {
// 유효성 검사
if(!isValid(date)) {
return "dayError";
}
// 2. 작업, 요일 계산
char day = getDay(date);
// 계산한 결과를 Model에 저장
// Model에 저장한 결과가 View(day.jsp)로 전달
model.addAttribute("myDate", date);
model.addAttribute("day", day);
// 3. 출력 결과를 보여줄 jsp파일로 이용해서 작업결과를 보여주라는 의미
return "day"; // /WEB-INF/views/yoil.jsp
}
@ModelAttribute 적용
@RequestMapping("/getDay")
public String main(@ModelAttribute MyDate mydate) {
// 유효성 검사
if(!isValid(mydate))
return "dayError";
// 출력
return "day";
}
위처럼 작업 결과를 Model에 저장하는 코드를 간소화할 수 있게 된다.
즉, year, month, day를 인스턴스 변수로 가지는 MyDate 객체가 존재하고 이를 매개변수로 받기 위해서는 위와 같이 컨트롤러를 생성하고, ?year=2024&month=01&day=05 로 요청을 하면 각각의 값이 핸들러의 MyDate 객체로 바인딩된다. (Setter가 존재해야 한다.)
@ModelAttribute 사용 시 장점
@RequestParam과 @ModelAttribute의 눈에 띄는 차이점은, 1:1 매핑이냐, 객체 매핑이냐 인 것으로 볼 수 있다.
그렇다면 @RequestParam으로 모두 전달받으면 되는데 @ModelAttribute로 사용자의 요청을 매핑하는 이유가 무엇일까?
사용자를 찾기 위해, 검색 조건을 요청에 담아 전달하는 경우를 예로 들어보자.
@ModelAttribute를 사용하지 않는 경우
@RestController
public class TestController {
@GetMapping("/")
public String getTestPage(@RequestParam int id,
@RequestParam String name,
@RequestParam String email,
@RequestParam String phone,
Model model) {
List<User> userList = userService.search(id, name, email, phone);
model.addAttribute("userList", userList);
return "test";
}
}
예를 들어 위와 같이 @RequestParam을 이용해 일일이 사용자의 요청을 매핑하는 경우를 먼저 살펴보자.
이때, 사용자를 찾기 위한 검색 조건이 늘어나거나 줄어드는 변경이 발생되었을 때의 문제점은 아래와 같다.
1. 다수의 변경점
3곳의 변경점이 생긴다.
- handler의 매개변수
- userService.search() 호출 시 넘겨주는 매개변수
- UserService 클래스의 search() 메서드의 시그니처
이들을 모두 일일이 변경하는 것은 꽤나 번거로운 작업이다.
2. 매개변수의 순서
매개변수가 많아지는 경우, 다른 타입의 매개변수라면 컴파일러가 에러를 잡아줄 테지만, 매개변수의 타입이 같은 경우, 순서가 바뀐다면 이는 코드의 양이 많아진다면 찾기 힘든 위험한 에러이다.
@ModelAttribute를 사용하는 경우
@Getter
@Setter
public class UserSearchForm {
private int id;
private String name;
private String email;
private String phone;
}
@RestController
public class TestController {
@GetMapping("/")
public String getTestPage(@ModelAttribute UserSearchForm userSearchForm,
Model model) {
List<User> userList = userService.search(userSearchForm);
model.addAttribute("userList", userList);
return "test";
}
}
위처럼 @ModelAttribute를 사용하는 경우 앞서 살펴본 단점을 해결해 준다.
만약 검색 조건이 추가되는 경우, UserSearchForm의 필드를 추가해 주면, 핸들러를 수정할 필요도 없으며, UserService의 search() 메서드의 시그니처 역시 수정할 필요가 없다. 왜냐? 각각의 값이 핸들러의 객체로 바인딩되기 때문이다.
@ModelAttribute를 사용할 때 body에 값을 담아 전송할 때 주의할 점
@ModelAttribute를 사용할 때 parameter에 값을 넣으면 잘 받아오지만 body에 값을 담아 전송하면 null값이 들어가는 경우가 있다.
위와 같이 Body에 값을 넣어서 전송해 봤다.
예상과는 다르게 name = null, age = 0 즉 값이 담기지 않았다. 어떻게 된 것일까?
@ModelAttribute에서는 Body를 이용해 전송하려면 Content_Type을 application/json이 아닌 multipart/form-data 형태로 전송해야만 한다.
따라서 Content-Type을 multipart/form-data 형태로 바꾼 뒤 테스트를 진행해 봤다.
이제야 예상했던 대로 name과 age에 값이 담겨서 반환되었다.
요약
바인딩 종류 | 특징 |
@ModelAttribute | - 변환이 아닌 바인딩을 시키기 때문에 변수들의 Setter 함수가 없으면 저장되지 않는다. - @ModelAttribute를 사용하여 HTTP body에 내용을 담기 위해서는 multipart/form-data 형식으로 전송해야 한다. - HTTP 파라미터들은 Setter를 통해 일대일 객체에 바인딩하기 위해 사용된다. - @ModelAttribute에는 매핑시키는 파라미터의 타입이 객체의 타입과 일치하는지를 포함한 다양한 검증(Validation) 작업이 추가적으로 진행된다. |
@RequestParam | - @RequestParam은 1개의 HTTP 요청 파라미터를 받기 위해서 사용한다. |
출처 / 참고
https://dkswnkk.tistory.com/457
[Spring] @RequestParam, @RequestBody, @ModelAttribute의 차이
서론 @ModelAttribute를 사용했을 때 parameter에 값을 넣으면 잘 받아오지만 body에 값을 담아 전송하면 null값이 들어가는 경우가 있었습니다. 이럴 때 @RequestBody를 사용하면 바르게 값이 잘 담아지는데
dkswnkk.tistory.com
https://galid1.tistory.com/769
Spring MVC - @ModelAttribute의 장점(@RequestParam와 @ModelAttribute)
이번 시간에는 사용자의 전달값을 핸들러의 매개변수로 매핑할때 사용되는 @RequestParam과 @ModelAttribute에 대해 알아보도록 하겠습니다. 1. 사용법과 예제 우선 각각의 어노테이션의 사용법과 예제
galid1.tistory.com
'SPRING' 카테고리의 다른 글
[Spring] Service, ServiceImpl 분리에 대한 내 생각 (2) | 2024.10.08 |
---|---|
[스프링 MVC] - 핸들러 매핑과 핸들러 어댑터의 구조 이해 (1) | 2024.01.15 |
의존관계 주입 - 자동, 수동의 올바른 기준 (0) | 2023.08.11 |