개인프로젝트를 진행하던 중 중, 어떤 의문이 생겼다.
데이터를 request로 보낼 때, Controller에서는 request를 어떻게 Dto로 바인딩하는거지?
위의 세 가지 어노테이션 중 어느 상황에 어떤 어노테이션을 사용해야하는거지????
http request를 보내는 방식에 대한 이해가 부족하다보니,
어떤 경우에 어떤 어노테이션을 왜 쓰는지 모르고 관습적으로 사용하곤 했다.
클라이언트가 서버에 데이터를 어떤 방식으로 보내는지에 따라서, 이를 적당한 DTO나 객체로 바인딩하는 방법이 달라진다.
현재 포스팅에서 중점적으로 다루어볼 방식은
Json, Http Parameter(path/queryString), 그리고 form 방식(get/post)이다.
Json 타입의 데이터를 받아오고, json타입으로 응답하는 방식은 Restful API에서 많이 쓰이는 방식이다.
Json 데이터는 보통 다음과같이 요청이 들어온다.
POST /login HTTP/1.1
Content-Type: application/json
{
"id" : "dallae",
"pwd" : "dallae1234!@#$"
}
클라이언트는 위처럼 Http Body에 application/json타입으로 서버에 데이터를 전달한다.
이 때, 서버는 요청된 Http Body를 읽어서 데이터를 가져와야 하므로 @RequestBody를 통해 읽어올 수 있다.
Parameter 방식은 URL에 그대로 노출하여 데이터를 전송한다.
URL 주소만을 읽어서 데이터를 바로 조회할 수 있기 때문에 Http Body를 조회할 필요가 없다.
Http Parameter 방식에는 1)QueryString과 2)Path가 있다.
POST /login?id=dallae&pwd=dallae1234!@#$ HTTP/1.1
QueryString은 위와 같이 주소 뒤에 ?이후 데이터를 전송하는 방식이다.
GET /product/1 HTTP/1.1
Path는 위와 같이 주소 뒤 /이후 데이터를 전송한다.
form은 get과 post방식이 각각 데이터를 다른 방식으로 보내게 된다.
GET / login?id=dallae&pwd=dallae1234!@#$ HTTP/1.1
get방식으로 form데이터를 전송하면, 위와 같이 form의 내용을 쿼리스트링으로 바꾸어 URL에 데이터를 담아 서버로 전송한다.
POST /login HTTP/1.1
Content-Type : application/x-www-urlencoded
id=dallae&pwd=dallae1234!@#$
post방식으로 form 데이터를 보내면, Http body에 application/x-www-urlencoded 타입으로 전송하게 된다.
URL이 아닌 Http Body에 x-www-urlencodede 형태로 데이터를 전송한다는 점은 다르지만,
보내는 데이터의 모양은 쿼리스트링형태이다.
위에서 봤듯이, 데이터마다 전송되는 방식이 각각 다르다.
서버는 클라이언트가 이렇게 각자 다르게 보낸 방식을 어떻게 받아와서 처리를 할까?
서버는 클라이언트가 보낸 데이터를 받아와서 처리할 수 있도록 객체로 변환하는 과정이 필요하다.
(예 : JSON 데이터 -> Java DTO 클래스)
이처럼 데이터를 객체로 매핑하는 과정을 역직렬화라고 한다. (반대는 직렬화)
이러한 과정도 그 데이터가 json 타입인지, 쿼리스트링인지, form데이터인지에 따라 달라진다.
차례차례 살펴보도록 하자.
@RequestBody
Json 데이터는 Http body를 통해 전송되기 때문에 @RequestBody
를 사용한다.
@RequestBody
는 Http body를 읽어와서 Jackson 라이브러리를 통해 Json 객체를 DTO와 매핑시킨다.
@PostMapping("/")
public ResponseEntity Login(@RequestBody RequestDto request){...}
@NoArgsConstructor
@Getter
public class RequestDto {
private String id;
private String pwd;
}
Spring은 Json 데이터를 역직렬화/직렬화 할 때 Jackson 라이브러리
의 ObjectMapper
를 사용한다.
Jackson 라이브러리
가 역직렬화 할 때,
DTO클래스는 생성자가 필요하고, getter 또는 setter로 데이터를 바인딩한다.
기본적으로 setter보다 getter를 통해 접근/수정하기 때문에 getter 사용을 권장한다.
역직렬화시 생성자는 없어도 가능하다.
하지만 스탠다드한 방법이 아니기 때문에 예상치 못한 다양한 오류가 발생할 수 있다.
응용 : @Builder 패턴
기본생성자가 선언되었을 때 모든 생성자가 필요하다.@NoArgsConstructor // @RequestBody 데이터 바인딩시 사용 @Getter // @RequestBody 데이터 바인딩시 사용 @AllArgsConstructor // @Builder 시 사용 @Builder public class RequestDto { private String id; private Stirng pwd; }
응답 시에도 Json 형식으로 데이터를 보내주는 경우가 대부분이다.
이 때 @ResponseBody
나 ResponseEntity
를 사용하게 되는데,
Jackson 라이브러리
는 Dto를 Json으로 변환하기 위해서 Dto의 getter를 사용한다.
@Getter
public class ResponseDto {
private String name;
private String phone;
}
응용 : @Builder패턴
@Getter // @ResponseBody 데이터 바인딩시 사용 @AllArgsConstructor // @Builder시 사용 @Builder public class ResponseDto { private String name; private String phone; }
Json과 Dto로 역직렬화/직렬화 시 사용하는 Jackson라이브러리는 주로 getter를 사용하여 데이터를 바인딩한다.
역직렬화시에는 private 기본 생성자와 public getter를 통해서,
직렬화시에서는 public getter를 통해서 데이터를 바인딩한다.
@RequestParam
, @PathVariable
, @ModelAttribute
클라이언트가 데이터를 전송할 때, URL에 파라미터로 보내는 형식이 있다.
이 때에는 @RequestParam
, @PathVariable
, @ModelAttribute
를 사용하여 역직렬화할 수 있다.
@RequestParam
@RequestParam
은 파라미터로 받은 값을 DTO로 받지 못하고, 변수로 하나하나 매핑할 수 있다.
@GetMapping("/login")
public ResponseEntity login(@RequestParam String id, String pwd) {...}
@PathVariable
@PathVariable
또한 마찬가지로 path로 받은 값을 DTO로 받지 못하고, 각각 변수로 매핑을 해주어야 한다.
@GetMapping("/board/{no}")
public ResponseEntity board(@PathVariable String id) {...}
@ModelAttribute
@ModelAttribute
는 파라미터로 받은 값을 DTO로 받아서 역직렬화할 수 있다.
더 자세한건 아래에서 알아보도록 하자!
@ModelAttribute
Spring은 form 데이터를 FormHttpMessageConverter
를 통해 자바객체와 바인딩한다.
즉, form 데이터는 기본적으로 @ModelAttribute를 사용하게 된다. (@ModelAttribute
생략 가능)
@GetMapping("/login")
public String Login(@ModelAttribute RequestDto request){...}
@ModelAttribute
는
form을 get방식으로 받는 경우에는 쿼리파라미터를 바인딩하고,
form을 post방식으로 받는 경우에는 application/x-www-urlencoded 형식의 쿼리파라미터를 바인딩한다.
즉, @ModelAttribute
는 이 두 가지 데이터 형식을 모두 바인딩할 수 있다.
@ModelAttribute
는 리플렉션을 사용하기 때문에, 기본생성자(NoArgs) + setter 또는 모든생성자(AllArgs) 통해 데이터를 바인딩한다.
@NoArgsConstructor @Setter
또는
@AllArgsConstructor
public class RequestDto{
priavte String id;
private String pwd;
}
getter는 클라이언트가 model에 담긴 DTO를 조회할 때 사용된다.
즉, request 데이터의 바인딩 자체에는 getter가 필요없다!
응용: @Builder패턴
@Getter // 클라이언트에서 값조회시 사용 @AllArgsConstructor // @ModelAttribute 데이터 바인딩, @Builder시 사용 @Builder public class ResponseDto { private String name; private String phone; }
@RequestBody
랑 @ModelAttribute
둘 다 바인딩이 가능한건가?아니다!
@RequestBody
를 사용하면 Content-Type이 application/json타입인 json 데이터를 Jackson라이브러리를 사용하여 역직렬화하게 된다.
form의 post데이터의 경우, Content-Type이 application/x-www-urlencoded 타입의 쿼리파라미터 데이터이기 때문에 @RequestBody
는 사용할 수 없다.
물론, MultiValueMap이라는 맵을 사용하여 @RequestBody를 사용할 수 있긴 하다.
@RequestBody MultiValueMap<String, String> data 로 역직렬화를 할 수 있지만, 비효율적인 사용이다.
즉, form의 post방식은 @ModelAttribute
를 사용하는 것이 바람직하다.
안녕하세요 list 검색할떄 ModelAttribute 사용하고 있는데 파라메터가 아무것도 없을때 NullPointException 발생하는데 해결방법 있을까요?