Spring

Controller에서 객체 매핑

기발개발 2023. 11. 6. 10:52

 

Person 클래스

@Getter
@Setter
@ToString
public class Person {
    private  String name;
    private  int age;
}

 

 

 <form> 태그에서  데이터를 전달해보자. 

 

 

 

@RequestParam,@ModelAttribute,@RequestBody 차이

스프링에서 객체-파라미터 매핑은 요청상황이나 응답상황 모두 MessageConverter가 담당한다.

상황에 따라 스프링이 적절한 MessageConverter의  구현체를 통해 객체-파라미터 매핑을 한다.

요청에서 @RequestParam, @ModelAttribute가 붙은면 FormHttpMessageConverter가,

@RequestBody가 붙으면 MappingJacksonHttpMessageConverter가 매핑을 한다.

(jackson-databind 라이브러리를 추가했다면)

 

 

@RequestParam - FormHttpMessageConverter

요청의 파라미터를 1:1로 매핑해준다. 

요청 파라미터인 name과 age 각각에 대해서 설정하는 것으로

객체에 직접 매핑할 수 없다.

@RequestMapping("/result")
public String post(@RequestParam Person person) {
    System.out.println(person);
    return "result";
}

GET,POST방식 둘다 person이라는 파라미터가 없다는 에러가 발생한다.

 

 

 

 

@ModelAttribute - FormHttpMessageConverter

요청의 여러 파라미터들을 한번에 Person의 필드들에 매핑해준다.

여러 파라미터들을 얻은 다음에 객체에 setting해주는 것이기 때문에 

setter가 없다면 값이 setting되지 않는다.

GET,POST 상관없이 잘 세팅된다.

public String post(@ModelAttribute("person") Person person) {
    System.out.println(person);
    return "result";
}

 

 

 

 

아무런 @없이 사용하는 경우는 파라미터-객체 매핑을 하는 경우

@ModelAttribute처럼 매핑한다고 보면 된다.

Model객체에 person을 담느냐 안 담느냐의 차이만 있을 뿐이라

아래 코드는 @ModelAttribute처럼 동작한다.

@RequestMapping("/result")
public String post(Model model, Person person) {
    System.out.println(person);
    model.addAttribute("person" , person);
    return "result";
}

 

 

 

@RequestBody  - MappingJacksonHttpMessageConverter

@RequestMapping("/result")
public String post(@RequestBody Person person) {
    System.out.println(person);
    return "result";
}

body 내용을 직접  변환하는 것이기 때문에  setter가 필요없다.

그렇기 때문에 Body가 없는 GET방식에서는 일반적으로 사용할 수 없다.   

GET방식에서는 RequestBody를 찾을 수 없다는 에러가 발생한다.

 

POST의 경우 Content type 'application/x-www-form-urlencoded;charset=UTF-8'

not supported 라는 에러가 발생한다.

기본적으로 요청의 content type은 application/x-www-form-urlencoded 인데 

이는 url?key1=val1 & key2=val2 의 형식으로 요청이 간다.

그런데 MappingJacksonHttpMessageConverter는 이러한 형태를

처리할 수 없기 때문에 에러가 발생한다.

 

 

 

 

 

AJAX에서의 요청

@ModelAttribute

@RequestMapping("/result")
public String post(@ModelAttribute Person person) {
    System.out.println(person);
    return "result";
}

 

 

contentType이  "application/x-www-form-urlencoded" 인 경우는

<form>태그 요청과 같은 결과가 나온다.

<script>
    $("#ajax").on("click" , function (){
        let data = { name : "김필자" , age : 30};
        $.ajax({
            url : "/result"
            ,type: "GET"  // POST
            // ,contentType : "application/x-www-form-urlencoded"
            ,data : data
            , success : function (data){
                console.log(data);
            }
            ,error : function (err){
                console.log(err);
            }
        });
    });
</script>

 

 

 

 

 

contentType을 "application/json"으로 변경하고

JSON.stringify(data)를 하면 요청자체는 GET방식에서는 에러가 나고 

POST방식에서는 에러는 안 나지만 값이 세팅되지 않는다.

<script>
    $("#ajax").on("click" , function (){
        let data = { name : "김필자" , age : 30};
        console.log(JSON.stringify(data));
        $.ajax({
            url : "/result"
            ,type: "POST"
            ,contentType : "application/json"
            ,data : JSON.stringify(data)
            , success : function (data){
                console.log(data);
            }
            ,error : function (err){
                console.log(err);
            }
        });
    });
</script>

 

 

 

 

 

 

@RequestBody

@RequestMapping("/result")
public String post(@RequestBody Person person) {
    System.out.println(person);
    return "result";
}

 

GET, POST방식 모두 그냥 <form>와 비슷하다. 

 

 

다음과 같이 contentType을 "application/json"으로 변경하고

body의 값이  key1=value1 & key2=value2 형태가 아닌 json형태로 바꿔주면

(JSON.stringify(data))  객체 매핑이 제대로 된다.

MappingJacksonHttpMessageConverter는 요청 body의 값을 자바의 객체로 변환해주기 때문.

body의 값을 읽는 것이기 때문에 GET방식은 안된다.

<script>
    $("#ajax").on("click" , function (){
        let data = { name : "김필자" , age : 30};
        console.log(JSON.stringify(data));
        $.ajax({
            url : "/result"
            ,type: "POST"
            ,contentType : "application/json"
            ,data : JSON.stringify(data)
            , success : function (data){
                console.log(data);
            }
            ,error : function (err){
                console.log(err);
            }
        });
    });
</script>

 

 

 

 

결론 

일반적인 <form>태그나 ajax요청에서는 굳이 @RequestBody를 사용하지 않아도 된다.

물론 클라이언트 측에서 application/json타입으로 보낸다면

@RequestBody를 사용하면 된다.

 

또 다음과 같이 복잡한 json형태를 다루는 ajax요청을 처리할 때

@RequestBody가 유용하게 사용된다

<script>
    $("#ajax").on("click" , function (){
        let array=[1,2,3];
        let map={name : "김필자" , age : 30};
        let data = { arrayData:  array , mapData :  map};
        console.log(JSON.stringify(data));
        $.ajax({
            url : "/result"
            ,type: "POST"
            ,contentType : "application/json"
            ,data : JSON.stringify(data)
            , success : function (data){
                console.log(data);
            }
            ,error : function (err){
                console.log(err);
            }
        });
    });
</script>

 

 

@RequestBody Map<String, Object> map으로 받아

ObjectMapper등을 이용해 parsing하면 된다.

@RequestMapping("/result")
public String post(@RequestBody Map<String,Object> map) throws JsonProcessingException {
    System.out.println(map);  //data1  array,  data2 map

    ObjectMapper mapper = new ObjectMapper();
    String arrayStr= map.get("arrayInJson").toString();
    List<Integer> list= mapper.readValue(arrayStr, new TypeReference<ArrayList<Integer>>(){}   ); // 자바스크립트에서 array였지만, list로도 가능
    System.out.println(list);


    String personStr = mapper.writeValueAsString( map.get("personInJson") );  
    // name=김필자  =>   name:김필자 ,  ObjectMapper는  문자열이 name:김필자  형태이어야만  자바객체로 변환가능
    Map<String , Object> person= mapper.readValue(personStr, new TypeReference< HashMap<String, Object>>() {}); //name,age는 String,int니까
    System.out.println(person); 


    return "result";
}

 

 

 

 

 

 

 

 

그외 기타 객체 매핑

 

자바스크립트배열 값을 컨트롤러에서 List<String>으로 받고 싶을 때

1. 배열을 그대로 전달

AJAX

var genre = [ "드라마", "액션", "무협" ];

$.ajax({
    url : "/ajax" , 
    data : {"searchGenre" : genre },
    success : function(data) {

    }
});//ajax

 

 

Controller

@ResponseBody
@RequestMapping(value = "/ajax")
public String ajax(@RequestParam(value="searchGenre[]")List<String> searchGenre) {
    System.out.println(searchGenre);
    return "";
}

 

 

 

2. 배열을 String으로 전달 

AJAX

var genre = [ "드라마", "액션", "무협" ];

$.ajax({
    url : "ajax" ,//상대경로,절대경로는 알아서 
    data : {"searchGenre" : genre.toString() },
    //data : {"searchGenre" : "드라마,액션,무협" },   이라고쓴거랑같다.
    success : function(data) {

    }
});//ajax

 

 

Controller

@ResponseBody
@RequestMapping(value = "/ajax")
public String ajax( List<String> searchGenre) {
    return "";
}