@ModelAttribute

konu·2023년 11월 26일
0

스프링

목록 보기
1/8
post-thumbnail

(객체를 담자...)

 

1. @ModelAttribute는?

간단하게 말하자면,

요청 파라미터 혹은 HTML 폼 데이터들을 내가 정의한 객체로 묶어주는 파라미터 어노테이션이다.

 

@PostMapping("/join")
    public String joinPage(@Valid @ModelAttribute MemberJoinRequestDto dto,
                           BindingResult bindingResult,
                           Model model) {
        if (memberValidator.validateJoin(dto, bindingResult).hasErrors())
            return "/member/join";

        memberService.join(dto);

        return "redirect:/";
    }
public class MemberJoinRequestDto {
    private String loginId;
    private String password;
    private String passwordCheck;
    private String nickname;

 

위 코드에서는 MemberJoinReqeustDto에 붙어 있다.

MemberJoinRequestDto는 회원가입에 필요한
네 개의 문자열 값들을 담고 있는 객체다.

 

 

위 사진처럼 사용자(?)가 폼을 채우고,
우리의 경우에는 POST 요청과 함께 네 개 데이터가 넘어올 것이다.

 

 

 

2. @ModelAttribute의 효능

1) 기본 데이터 여럿을 하나의 객체로 묶을 수 있다.

 

위 장점은
@RequestParam과 비교할 때 제대로 알 수 있다.

 

@GetMapping()
    public String search(@RequestParam(value = "query", required = false) String query,
                         Model model,
                         @RequestParam(defaultValue = DEFAULT_SEARCH_PAGE) int page,
                         @RequestParam(defaultValue = DEFAULT_SEARCH_SIZE) int size) {
        if (query != null)
            model.addAttribute("result", movieService.search(query, page, size));

        return "searchForm";
    }

 

위 search() 메서드의 시그니쳐를 보면
@RequestParam으로 "query"란 값의 파라미터를 String으로 받고 있다.

String은 기본형(아니긴 하지만).
UserJoinRequestDto 객체와는 확연히 다르다.

그러면 만약에 첫번째 예시인 joinPage() 메서드에서
@RequesetParam을 사용한다면?

 

@PostMapping("/join")
    public String joinPage(@RequestParam String loginId,
                           @RequestParam String password,
                           @RequestParam String passwordCheck,
                           @RequestParam String nickname) {
        memberService.join(loginId, password, passwordCheck, nickname);

        return "redirect:/";
    }

 

위처럼 네 개 필드 각각을 파라미터로 모두 받아야 한다.
이미 그것만으로도 메서드 시그니쳐가 지저분해서 너무 슬프다.

그런데 두 가지 문제가 더 발생한다.

 

1) 보안상의 문제

@RequestParam은 말그대로 url 요청의 파라미터를 필요로 한다.

위 사진은 유튜브 http 요청을 예시로 든 것인데,
"search_query=게이트플라워즈"라고 된 것을 볼 수 있을 것이다.

이처럼 http 요청에 아주 공개적으로
회원의 아이디와 비밀번호가 넘어가게 된다면
보안에 큰 문제를 일으킬 수 있다.

 

2) 유지보수성

@PostMapping("/join")
    public String joinPage(@RequestParam String loginId,
                           @RequestParam String password,
                           @RequestParam String passwordCheck,
                           @RequestParam String nickname) {
        memberService.join(loginId, password, passwordCheck, nickname);

        return "redirect:/";
    }

 

위 코드를 다시 보자.
여전히 네 개 파라미터를 http 요청에서 끌어다 쓰고 있는데,
만약에 회원가입에 전화번호를 추가해야 한다면 어떻게 해야 할까?

답은 간단하다.

@PostMapping("/join")
    public String joinPage(@RequestParam String loginId,
                           @RequestParam String password,
                           @RequestParam String passwordCheck,
                           @RequestParam String nickname
                           @RequestParam String phoneNumber) {
        memberService.join(loginId, password, passwordCheck, nickname, phoneNumber);

        return "redirect:/";
    }

 

위처럼 파라미터를 하나 추가해야 한다.
문제는 MemberService의 join()에까지 악영향을 끼치고 있다는 점이다.

이처럼 @RequestParam은 String이나 int 같은
기본적인 타입만 받을 수 있기 때문에 확장성이 매우 딸린다.

 

 

2) BindingResult를 활용해서 클라이언트의 데이터를 validate할 수 있다.

 

@PostMapping("/join")
    public String joinPage(@Valid @ModelAttribute MemberJoinRequestDto dto,
                           BindingResult bindingResult) {
        if (memberValidator.validateJoin(dto, bindingResult).hasErrors())
            return "/member/join";

        memberService.join(dto);

        return "redirect:/";
    }

 

위 코드로 다시 돌아가자.
@ModelAttribute는 이제 익숙한데,
BindingResult는 아직 소개하지 않았다.

BindingResult는 말그대로
클라이언트가 POST와 함께 보낸 데이터를 객체로 바인딩할 때
발생하는 결과를 담는 객체다.

joinPage()에서는 이 BindingResult를 활용해서
validateJoin() 메서드의 파라미터로 넣고 있다.

 

 

회원가입을 하다 보면 위와 같은 상황 말고도

아이디가 이미 존재한다든가
10자 길이를 넘지 않아야 한다든가,

'비밀번호 확인' 란에 비밀번호와 다른 값을 넣었다가
다시 회원가입 페이지로 돌아가거나 하는 일이 빈번하다.

이러한 각각의 값들마다 가지는 제한 사항을 확인하고,
제한 사항에 위반될 경우 BindingResult에 Error 객체를 넣어줄 수 있다.

 

3) Model에 자동으로 더해진다.

	@PostMapping("/item/add")
    public String add(@ModelAttribute("item") Item item) {
        itemRepository.save(item);

        return "/item/detail";
    }

 

위 메서드는 POST 요청을 통해 넘어온 HTML Form 정보를 저장해준다.
그리고 "/item/detail" URI 경로에 있는 HTML을 호출해서 보여준다.

이 때, HTML에는 add()를 통해 새롭게 더해진 아이템을 보여주려고 한다.
그런데 model.addAttribute("item", item)과 같은 로직이 없음에도
@ModelAttribute 덕분에 자연스럽게 detail 파일에 item이 전달된다.

 


(그래서?)

 

3. 결론

결론은 무척 간단하다.

회원 정보와 같은 민감한 정보가 포함되었거나,
클라이언트가 채워준 두 개 이상 종류의 데이터가
한꺼번에 POST 요청으로 넘어갈 때에는 @ModelAttribute를 사용한다.

하지만
검색 폼에 채운 쿼리 값과 같은 검사도 필요 없는
단순한 문자열인 경우에는 @RequestParam에 String 조합으로
깔끔하게 받는 것이 낫다..!

profile
日日是好日

0개의 댓글