본문 바로가기

SpringBoot

ch2 08. @RequestParam과 @ModelAttribute - 이론

1. WebDataBinder

@RequestMapping("/getYoilMVC5")
public String main(@ModelAttribute MyDate date, BindingResult result) {

http://localhost:8080/requestParam5?year=2023&month=1&day=2

 

name value
"year" "2023"
"month" "1"
"day" "2"

이런식으로 map으로 저장된다. 값이 여러 개 들어갈수 있기 때문에 '배열'로 저장된다. 
대부분은 하나만 들어온다. 
URL은 문자열 -> map도 <String, String>타입으로 저장된다.

 

현재  main메소드의 매개변수 타입은 Mydate이다. DispatcherServlet이 이 매개변수 타입을 보고 MyDate의 객체를
만든다. 

public class MyDate {
	private int year;
	private int month;    
	private int day;
    // ... getter & setter 
 }

getYoilMVC5라는 요청이 왔을 때 연결된 메소드를 호출하고, 호출 시 메소드의 매개변수를 DispatcherServlet이 보고 
이 메소드는 MyDate 객체를 필요로 하는다는 걸 알고 생성해준다. 

 

     MyDate

0
0
0

year, month, day를 인스턴스 변수로 갖는 MyDate 객체가 생성된다. 
생성하면 처음엔 기본값으로 int 타입이니 0이 저장된다. 

그 다음에는 객체의 인스턴스 변수 이름을 보고 같은 걸 찾는다. 
그러면 쿼리스트링으로 데이터를 가지고 구성한 맵에서 객체의 인스턴스 변수의 이름과 일치하는 걸 찾아서 넣어준다. 
String -> int로 변환은 자동으로 수행해준다. 

 

name value
"year" "2023"
"month" "1"
"day" "2"

=> 

 

     MyDate

2023
1
2

 

원하는 타입으로 자동 변환을 수행해주는데 이걸 'WebDataBinder'가 수행해준다. 
웹에서 쓰이는 데이터 바인더이며 하는 일은 2가지이다. 
1) 타입 변환

- 타입 변환이 필요하다면 변환

기본적으로 수십가지 타입 변환이 등록되어 있다. 거기에 등록된 타입인지 보고 맞는게 있다면 자동으로 변환 수행

작업하다가 에러가 생기면 BindingResult라는 객체에 저장한다. 

(날짜같은 경우는 복잡해서 직접 등록하는 경우가 많음)

 

2) 데이터 검증

- 데이터가 올바른지 검증

- Validator가 존재
(아이디, 패스워드 같은 검증을 넣어서 사용할 수 있다. 

BingingResult 타입의 매개변수로 적어주면 메소드 내에서 확인할 수 있고, DispatcherServlet이 자동으로 넣어준다.
그러면, 이 안에서 BindingResult객체를 보고 어떤 에러가 발생했는지 확인할 수 있다. 

(안써도 상관 X)

 

 

2. @ModelAttribute

- 적용 대상을 Model의 속성으로 자동 추가해주는 어노테이션

	@RequestMapping("/getYoilMVC5")
//  public String main(@ModelAttribute("myDate") MyDate date, Model m) {
	public String main(@ModelAttribute MyDate date, Model m) { // 위와 동일
    // ...
    	char yoil = getYoil(date);
        
        // 3. Model에 작업한 결과를 저장
        m.addAttribute("myDate", date); // 수동 추가
        m.addAttribute("yoil", yoil); // 수동 추가
        
        // 4. 작업 결과를 보여줄 뷰의 이름을 반환
        return "yoil";
     }

MyDate 객체 앞에 @ModelAttribute를 붙이면 이 객체를 Model에 자동으로 저장해준다. 

원래는 m.addAttribute("MyDate", date); 모델을 추가해줬는데 해당 문장을 생략해도 되게 된다. 

 

 

- 반환 타입 또는 컨트롤러 메소드의 매개변수에 적용 가능

private @ModelAttribute("yoil") char getYoil(Mydate date) {
	return getYoil(date.getYear(), date.getMonth(), date.getDay());
 }

 메소드 앞에 붙이면 이 메소드가 자동으로 호출되고, 그 결과를 모델에 저장해준다.
저장할 때 Key를 "yoil"로 저장해주는 것. 

그러면  m.addAttribute("yoil", yoil); 문장으로 수동 추가 해주는 문장을 생략해도 된다. 

	@RequestMapping("/getYoilMVC5")
//  public String main(@ModelAttribute("myDate") MyDate date, Model m) { // key이름 생략가능
	public String main(@ModelAttribute MyDate date, Model m) { // 위와 동일
    // ...
//  	char yoil = getYoil(date);
        
        // 3. Model에 작업한 결과를 저장
//      m.addAttribute("myDate", date); // 수동 추가
//      m.addAttribute("yoil", yoil); // 수동 추가
        
        // 4. 작업 결과를 보여줄 뷰의 이름을 반환
        return "yoil";
     }

- char yoil = getYoil(date); 메소드를 호출해주는 문장도 생략된다. 

 

- public String main(@ModelAttribute("myDate") MyDate date, Model m)에서 타입인 MyDate의 첫 글자를 소문자로 바꿔서 속성 이름으로 사용하기 때문에 "myDate" 키 값 생략 가능하다. (대부분 생략. 다른 이름 사용 시 적어주기)

 

- public String main(@ModelAttribute MyDate date, Model m)문장에서 @ModelAttribute 전체를 생략할수도 있다. 

컨트롤러의 RequestMapping된 메소드의 매개 변수 타입이 '기본형'일 때는 @RequestParam이 생략,
'참조형'일 땐 @ModelAttribute가 생략됐다고 보면 된다. 

MyDate는 참조형이기 때문에 @ModelAttribute를 사용하지 않아도 @ModelAttribute가 붙었다고 보고
Model에 자동추가를 해준다. 

 

 

이처럼 어노테이션을 붙이는 것만으로도 많은 코드를 생략할 수 있다. 

 

 

3. RequestParam

- 요청의 파라미터를 연결할 매개변수에 붙이는 어노테이션

    @RequestMapping("/requestParam")
    public String main(HttpServletRequest request) {
        String year = request.getParameter("year");
//		http://localhost/ch2/requestParam         ---->> year=null
//		http://localhost/ch2/requestParam?year=   ---->> year=""
//		http://localhost/ch2/requestParam?year    ---->> year=""
        System.out.printf("[%s]year=[%s]%n", new Date(), year);
        return "yoil";
    }

요청 파라미터라는 것은 year와 같은 걸 의미한다. 

요청할 때 쿼리스트링에 year라는 값을 주면 원래 request 객체를 받아와서 getParameter()하고 "year"를 지정해줘야 한다.

매번 request.getParameter()를 이용해서 읽어오는게 힘드니 매개 변수에 String year를 적어주고 
year=2023 이런식으로 주면 해당 값이 year에 들어오게 된다. 

    @RequestMapping("/requestParam2")
//	public String main2(@RequestParam(name="year", required=false) String year) {   // 아래와 동일
    public String main2(String year) {
//		http://localhost/ch2/requestParam2         ---->> year=null
//		http://localhost/ch2/requestParam2?year    ---->> year=""
        System.out.printf("[%s]year=[%s]%n", new Date(), year);
        return "yoil";
    }

이와 같이 수정하면  request.getParameter()를 사용하지 않아도 된다. 
넘어오는 값 중 year가 있으면 그 값을 대입해준다. 이걸 WebDataBinder가 수행해준다. 

    @RequestMapping("/requestParam3")
//		public String main3(@RequestParam(name="year", required=true) String year) {   // 아래와 동일
    public String main3(@RequestParam String year) {
//		http://localhost/ch2/requestParam3         ---->> year=null   400 Bad Request. required=true라서
//		http://localhost/ch2/requestParam3?year    ---->> year=""
        System.out.printf("[%s]year=[%s]%n", new Date(), year);
        return "yoil";
    }

원래는 이렇게 @RequestParam을 써줘야하는데 이게 생략된 것이다. 
아예 안쓰면 @RequestParam(name="year", required=false)

쓰면 @RequestParam(name="year", required=true) 이렇게 required가 필수가 된다. 
그러면 이 경우는 요청하는 쪽에서 year를 꼭 넘겨줘야한다. (클라이언트 에러. 400번대)

year만 써주고 값을 안주는 경우 빈문자열이 넘어와 에러가 생기지 않는데, 이렇게 애매하게 쓰는 것보다는 
아예 붙이지 않는 것이 낫다. 

(웬만하면 안쓰는게 좋음. 필요할 때 이런 옵션이 있으니 참고정도)

 

 

   @RequestMapping("/requestParam6")
    public String main6(int year) {
//		http://localhost/ch2/requestParam6        ---->> 500 java.lang.IllegalStateException: Optional int parameter 'year' is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type.
//		http://localhost/ch2/requestParam6?year   ---->> 400 Bad Request, nested exception is java.lang.NumberFormatException: For input string: ""
        System.out.printf("[%s]year=[%s]%n", new Date(), year);
        return "yoil";
    }

이번에는 매개변수 타입이 int인 경우이다. (이전에는 String)

이럴 때는 어노테이션을 붙이지않으면 required=false로 설정되는데 year을 입력하지 않았을 경우 서버 에러가 발생한다.
(500번대)

requied=false이니 필수가 아니기 때문에 year을 안줘도 클라이언트는 잘못한게 없다.

아무것도 안쓰면 year=null이 되고, 서버는 null을 받아서 int타입으로 바꾸려고 하니 서버 에러가 발생하는 것이다. 

 

만약 year의 값을 입력하지 않은 경우는 빈문자열 ""이 값이 되는데 이는 int로 변환할 수 없으므로, 
사용자가 값을 잘못 넘겨준걸로 판단해 클라이언트 에러가 발생한다. 

 

 

사용자가 값을 넘겨줬을 때 에러가 발생하지 않게 하기 위해선 defaultValue로 기본 값을 설정해준다. 

    @RequestMapping("/requestParam11")
    public String main11(@RequestParam(required=false, defaultValue="1") int year) {
//		http://localhost/ch2/requestParam11        ---->> year=1
//		http://localhost/ch2/requestParam11?year   ---->> year=1
        System.out.printf("[%s]year=[%s]%n", new Date(), year);
        return "yoil";
    }

 defaultValue를 설정해줬기 때문에 여기에는 다 에러가 없다. 

 

=> @RequesParam은 defaultValue를 주거나, 필수로 받아야 하는 값을 지정하는 경우가 아니면 잘 사용하지 않는다.