요청 파라미터 매핑, 에러, 유효성 검사 다루기(@ModelAttribute, @BindingResult, @Valid)
1. 요청 데이터 매핑 (@ModelAttribute)
@ModelAttribute
는 @RequestParam
처럼 요청에 존재하는 데이터를 매개변수로 매핑할수 있도록하는 어노테이션입니다. @ModelAttribute
는 기본 타입만이 아닌, 객체로도 자동으로 맵핑을 시켜줍니다.
하지만 @ModelAttribute
어노테이션의 경우에는 데이터가 반드시 RequestParameter
여야만 매핑해주는 것이 아니라 UriPath
, Session
의 데이터들을 자동으로 매핑해준다는 장점이 있습니다. 예제를 통해 알아보도록 하겠습니다.
1.1 PathUri 맵핑하기
첫번째로 @ModelAttribute
를 이용하여 Uri Path
에 존재하는 데이터를 Event 객체로 맵핑해보겠습니다.
xpublic class Event {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName(){
return this.name;
}
}
우리가 맵핑할 Event 객체입니다.
우리가 통과해야할 테스트 코드를 먼저 작성해보도록 하겠습니다. 가장 low한 단계인 url에 매핑되는 핸들러가 존재한다면 통과하는 테스트코드부터 작성합니다.
xxxxxxxxxx
SpringRunner.class) (
public class HelloControllerTest {
MockMvc mockMvc;
public void testModelAttribute() throws Exception{
mockMvc.perform(get("/model"))
.andExpect(status().isOk());
}
}
/model
이라는 uri에 요청시 응답하는 handler가 있는지 테스트 코드를 작성합니다. 이 코드는 지금은 당연히 실패를 할 것 입니다.
xxxxxxxxxx
"/model") (
public String model(){
return "ok";
}
위의 코드를 통과하기 위해서, /model
에 맵핑되는 핸들러를 작성합니다. 테스트에 성공합니다.
xxxxxxxxxx
public void testModelAttribute() throws Exception{
mockMvc.perform(get("/model/jjy"))
.andExpect(status().isOk());
}
다음으로 통과해야할 테스트 코드입니다. 우선 uri에 추가적으로 path를 통해 name를 받는 핸들러가 존재하는지의 테스트 코드를 작성합니다. 당연히 테스트에 실패합니다.
xxxxxxxxxx
"/model/{name}") (
public String model(){
return "ok";
}
핸들러를 위와같이 수정하여 name라는 이름으로 url로 부터 데이터를 가져옵니다.
xxxxxxxxxx
public void testModelAttribute() throws Exception{
mockMvc.perform(get("/model/jjy"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("name").value("jjy"));
}
이번에는 테스트코드를 다음과 같이 수정하여, 핸들러의 응답으로 json데이터가 오는데, 그 데이터중 name이라는 키값의 value는 jjy 데이터가 응답되도록 합니다.
xxxxxxxxxx
"/model/{name}") (
public Event model( Event event){
return event;
}
핸들러를 위와 같이 수정합니다. @ResponseBody를 통해 응답 본문에 응답 데이터를 담도록하고, @ModelAttribute를 이용하여 요청을 Event객체로 맵핑합니다. 마지막으로 event를 return 합니다.
1.2 Parameter로 전달받는 데이터 맵핑하기
이번에는 사용자가 Parameter에 데이터를 담아 요청하는 경우, @ModelAttribute를 이용하여 객체로 맵핑하는 방법을 알아보겠습니다.
xxxxxxxxxx
public class Event {
private String name;
private Integer age;
public void setName(String name) {
this.name = name;
}
public String getName(){
return this.name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
먼저 여러 데이터를 맵핑하기 위해 Event 클래스를 위와같이 수정했습니다.
xxxxxxxxxx
public void testModelAttribute() throws Exception{
mockMvc.perform(get("/model?name=jjy&age=10"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("name").value("jjy"))
.andExpect(jsonPath("age").value(10));
}
이번에는 그냥 한번에 통과해야할 테스트 코드를 작성해보겠습니다. parameter로 데이터를 전달하기 위해서는 위와 같이 작성할 수 있습니다. (?name=jjy&age=10
)
xxxxxxxxxx
public void testModelAttribute() throws Exception{
mockMvc.perform(get("/model")
.param("name", "jjy")
.param("age", "10"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("name").value("jjy"))
.andExpect(jsonPath("age").value(10));
}
또는 이렇게도 테스트 코드를 작성할 수도 있습니다.
xxxxxxxxxx
"/model") (
public Event model( Event event){
return event;
}
핸들러를 다음과 같이 작성한 후 테스트를 합니다.
2. 에러 다루기(@BindingResult)
서버 개발자는 사용자가 항상 우리가 원하는 형식의 데이터를 보낸다고 생각하면 안됩니다. 예를 들면 우리는 앞서 만든 Event 객체의 age
에는 정수형 값이 전달되어 어떠한 처리를 할것입니다. 하지만 사용자가 age에 int형 값이 아닌 String
형태의 값을 보냈다면 어떨까요?
xxxxxxxxxx
public void 정수형변수에_문자열바인딩_테스트() throws Exception{
mockMvc.perform(get("/model")
.param("name", "jjy")
.param("age", "asd"))
.andExpect(status().isOk())
.andDo(print());
}
위와 같이 테스트를 하면 당연히 binding에 실패하며 400error가 나타날 것입니다.
에러다루기(BindingResult)
위 상황의 문제점은 그냥 error만 나타나고 핸들러 메소드가 끝난다는 점입니다. 이 때 직접 에러를 핸들링 하고 싶다면, Handler 메소드에 BindingResult
매개변수를 추가해주면 됩니다.
*주의
꼭 @ModelAttribute의 바로 오른쪽 매개변수이어야 합니다.
xxxxxxxxxx
"/model") (
public Event model( Event event, BindingResult bindingResult){
return event;
}
위의 코드와 같이 그냥 BindingResult 매개변수만 추가해주면 끝입니다. 테스트 결과를 보겠습니다.
상태코드 200을 반환하며 테스트에 성공하는 것을 볼 수 있습니다. 결과값에 age
가 바인딩 되지 못해 null을 나타내는것을 볼 수 있습니다.
xxxxxxxxxx
"/model") (
public Event model( Event event, BindingResult bindingResult){
if(bindingResult.hasErrors())
bindingResult.getAllErrors().forEach(v -> {
System.out.println(v.toString());
});
return event;
}
BindingResult를 이용해 에러들을 출력해보겠습니다. 위와 같이 age 변수에 string을 바인딩 하려다보니 NumberFormatException이 나타나는것을 볼 수 있습니다. 위의 코드를 이용해 사용자가 입력한 값이 허용되지 않는 경우의 처리를 할 수 있습니다.
3. 값 유효성 검사 및 처리(@Valid, @Validated)
앞선 상황(Integer에 String을 바인딩) 같이 특별한 에러를 발생시키는 경우가 아닌, 특정 값을 사용하지 못하고 싶은 경우에는 유효성 검사
를 통해서 사용자의 입력값을 제어할 수 있습니다. 바로 @Valid
, @Validated
어노테이션을 이용하는 것 인데요. 예제를 통해 알아보도록 하겠습니다.
3.1 @Valid 어노테이션으로 Null 값 검증하기
사용자가 값을 아에 입력하지 않는 경우에는 서버에서 어떤 식으로 처리가 될까요? 정답은 null로 맵핑한다 입니다. 테스트 코드를 통해 알아보도록 하겠습니다.
xxxxxxxxxx
"/model") (
public Event model( Event event){
return event;
}
위의 코드는 간단히 사용자의 요청에 담긴 값들을 Event 객체로 맵핑한뒤 Http Body에 그 데이터를 담아 반환하는 핸들러입니다.
xxxxxxxxxx
public void 사용자가_아무런_입력을_하지않은_경우_테스트() throws Exception{
mockMvc.perform(get("/model"))
.andExpect(status().isOk())
.andDo(print());
}
사용자가 아무런 입력을 하지 않은 경우입니다. 200 코드가 나타나며 테스트에 성공합니다. 하지만 Body 담겨온 Json의 각 데이터들이 모두 null
입니다.
xxxxxxxxxx
public class Event {
private String name;
private Integer age;
public void setName(String name) {
this.name = name;
}
public String getName(){return this.name;}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
우선 Dto를 수정합니다. 꼭 사용자로부터 입력받아야할 데이터에 해당하는 필드에 @NotNull
어노테이션을 추가합니다.
xxxxxxxxxx
"/model") (
public Event model( Event event, BindingResult bindingResult){
if(bindingResult.hasErrors()) {
System.out.println("============= ERROR =============");
bindingResult.getAllErrors().forEach(v -> {
System.out.println(v.toString());
});
}
return event;
}
핸들러의 매개변수 앞에 @Valid
라는 어노테이션을 추가로 등록합니다. 또 테스트시 어떤 에러가 나타나는지 콘솔에 출력하기 위해서, BindingResult를 매개변수에 추가하고, bindingResult를 이용해 Error의 내용을 출력하는 코드를 작성합니다.
name, age
필드가 null임을 알려주는 error가 콘솔에 나타납니다. 현재는 BindingResult를 매개변수로 받고 있기 때문에 일단 테스트가 통과한 것입니다.
3.2 @Validated를 이용해 유효성 검사 그룹을 지정
@Validated
를 이용하면 그룹을 지정하여 유효성 검사를 수행할 수 있습니다. 예제를 통해 알아보겠습니다.
우선 interface 로 Group들을 생성합니다. Name과 Age 각각에 적용할 Group을 생성했습니다. 그 후 유효성 검사 어노테이션의 옵션중 하나인 groups
를 이용하여 앞서 생성한 interface중 포함될 그룹의 interface.class를 지정합니다.
x
"/model") (
public Event model( (Event.ValidateAgeNotNull.class) Event event, BindingResult bindingResult){
if(bindingResult.hasErrors()) {
System.out.println("============= ERROR =============");
bindingResult.getAllErrors().forEach(v -> {
System.out.println(v.toString());
});
}
return event;
}
Handler 메소드에서는 @Valid
를 @Validated
로 변경합니다. @Validated
의 속성으로는 Event.GroupInterface.class
를 입력합니다. 위의 핸들러는 사용자가 보내는 요청 데이터에 Age값이 Null인지 확인을 하게 됩니다.
public void 사용자가_아무런_입력을_하지않은_경우_테스트() throws Exception{
mockMvc.perform(get("/model"))
.andExpect(status().isOk())
.andDo(print());
}
Name과 Age를 모두 Null로하여 테스트를 진행합니다. 결과로 Age값이 Null이라는 에러만이 나타납니다.
'FrameWork > Spring MVC' 카테고리의 다른 글
SpringMVC - 중복 서브밋 처리(새로고침시 POST 재요청 방지 : PRG 패턴) (0) | 2019.05.23 |
---|---|
SpringMVC - 핸들러 메소드 - 3 (사용자 요청 데이터 Error 상황처리) (0) | 2019.05.23 |
SpringMVC - Spring MVC 설정 - 4 (HttpMessageConverter) (0) | 2019.05.19 |
SpringMVC - 핸들러 메소드 - 1 (Mapping 어노테이션, 확장자, 요청헤더) (1) | 2019.05.16 |
SpringMVC - Spring Mvc, H2, JPA, MyBatis 연동 환경 구성하기 (2) | 2019.05.13 |