2019. 9. 10. 15:22ㆍWeb/Spring
이번 포스팅은 @RequestParam과 @ModelAttribute의 차이점에 대해 다루어볼 것이다. 이번에 다루어볼 내용은 특정 유저관련 컨트롤러 코드를 작성하여 살펴볼 것이다.
작업환경은 MacOS+intelliJ로 구성하였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/insert")
public Object insertUser(@ModelAttribute("findUser") User user, BindingResult bindingResult){
if(bindingResult.hasErrors()){
log.info("binding error");
return "invalidParam";
}
if(user.getId() == 1){
user.setLastName("yoon");
user.setAge(28);
user.setLevel(Level.BASIC);
return user;
}
return null;
}
@GetMapping("/insert2")
public Object insertUser(@RequestParam int id, @RequestParam String firstName, @RequestParam String lastName){
return "RequestParamBinding";
}
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
class User{
private int id;
private String firstName;
private String lastName;
private int age;
private Level level;
}
|
cs |
위와 같은 코드가 있다. 세부적인 로직은 제외하고 파라미터를 바인딩하는 부분을 집중해보자. 우선 @RequestParam과 @ModelAttribute의 파라미터 바인딩에는 큰 차이가 있다. 바로 객체로 받는냐, 혹은 쿼리스트링을 하나하나 바인딩 받느냐의 차이이다. 이것은 코드의 가독성에 아주 큰 차이가 있다. 두번째 차이점은 @ModelAttribute는 사용자가 직접 View에 뿌려질 Model객체를 직접 set하지 않아도 된다는 점이다. @ModelAttribute는 스프링이 직접 ModelMap에 객체를 넣어주기 때문이다. 이러한 바인딩 차이점 이외에 한가지 아주 큰 차이점이 있다.
매핑을 보면 @ModelAttribute에는 BindingResult라는 인자가 들어가있는 것을 볼 수 있다. 하지만 @RequestParam은 없다. 과연 무슨 말일까?
@ModelAttribute는 사실 파라미터를 검증할 수 있는 기능을 제공한다. 이 말은 파라미터 바인딩에 예외가 발생하여도 혹은 파라미터 바인딩은 문제없이 됬으나 로직처리에 적합하지 않은 파라미터가 들어오는 등 이러한 파라미터를 검증할 수 있는 기능이 있다. 만약 파라미터 바인딩에 예외가 발생하였을 때, @ModelAttribute와 @RequestParam의 차이점을 보자. 전자는 바인딩 예외를 BindingResult에 담아준다. 즉, 4xx 예외가 발생하지 않는 것이다. 하지만 @RequestParam 같은 경우 바인딩 예외가 발생하는 순간 TypeMisMatch 예외를 발생시켜 사용자에게 4xx status code를 전달한다.
그러면 개발자는 파라미터를 검증하는 로직을 컨트롤러 코드와 따로 분리하여 바인딩된 파라미터의 값의 유효성을 검사할 수 있게 해준다. 만약 @RequestParam이라면 직접 비즈니스 로직내에서 파라미터를 검증하는 코드가 들어가야 할 것이다.
바인딩 예외 처리의 차이점을 간단한 테스트 코드로 테스트 해보았다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserControllerTest {
private static final String FINDBYID_MODELATTRIBUTE_REQUEST_URI = "/user/find";
private static final String FINDBYID_REQUESTPARAM_REQUEST_URI = "/user/find2";
private User validParamForFindByUserId ;
private MockMvc mockMvc;
@Before
public void init(){
this.mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
}
@Before
public void initParam(){
validParamForFindByUserId = new User(1,"yeoseong","yoon",28,Level.valueOf(1));
}
@Test
public void paramVaildationWhenInvalidInputParamWithModelAttribute() throws Exception {
MvcResult createResult = mockMvc.perform(get(FINDBYID_MODELATTRIBUTE_REQUEST_URI+"?id=abc"))
.andDo(print())
.andExpect(content().string("invalidParam"))
.andReturn()
;
}
@Test
public void paramValidationWhenInvalidInputParamWithRequestParam() throws Exception {
MvcResult createResult = mockMvc.perform(get(FINDBYID_REQUESTPARAM_REQUEST_URI+"?id=abc"))
.andDo(print())
.andExpect(status().is4xxClientError())
.andReturn()
;
}
}
|
cs |
위 테스트를 돌려보면 우리가 기대하는 바인딩 예외처리 테스트가 무사히 통과할 것이다.
'Web > Spring' 카테고리의 다른 글
Spring - Spring Cache 추상화(스프링 캐시 추상화),@Cacheable,@CacheEvict,@CachePut (0) | 2019.09.11 |
---|---|
Spring - Model Validation 방법!(파라미터 Validation),@Valid (0) | 2019.09.10 |
Springboot - Bean Scope(싱글톤, 프로토타입) ? (0) | 2019.08.17 |
Spring - Field vs Constructor vs Setter Injection 그리고 순환참조(Circular Reference) (3) | 2019.08.17 |
Springboot - DB Datasource 암호화/복호화(application.properties) (0) | 2019.08.05 |