2 분 소요

스프링 애플리케이션에서 Request Parameter를 어떻게 다룰 수 있을까요?

@GetMapping("/users")
public Page<User> getUsers(
    @RequestParam(name = "name", required = false) String userName,
    @RequestParam(name = "phone", required = false) String userPhone,
    // ...
) {
    // ...
}

@RequestParam 어노테이션을 사용하면 매우 간단합니다. 하지만 파라미터의 수가 많아진다면 코드의 가독성을 떨어뜨립니다.

이를 해결할 방법으로 모델 클래스를 사용할 수 있습니다.

@Data // lombok
public class GetUserModel {

    private String userName;
    private String userPhone;
    // ...
}

@GetMapping("/users")
public Page<User> getUsers(GetUserModel getUserModel) {
    // ...
}

모델 클래스를 사용해서 Request Parameter 매핑을 했지만 @RequestParam이 제공하는 name의 기능은 사용할 수 없어 클라이언트는 모델의 멤버변수명과 동일하게 요청해야 합니다.

이 경우에 해결할 수 있는 방법을 공유합니다.
(아래에 나오는 테스트에서는 모델을 그대로 응답하는 컨트롤러 메소드를 사용했습니다.)

방법 1. Jackson을 이용한 Map 매핑

이 방법은 모델 클래스를 사용하지 않습니다.

@Getter // lombok
public class GetUserModel {

    @JsonAlias("name")
    private String userName;

    @JsonAlias("phone")
    private String userPhone;
    // ...
}
import com.fasterxml.jackson.databind.ObjectMapper;

@GetMapping("/users")
public Page<User> getUsers(@RequestParam Map<String, String> parameterMap) {
    ObjectMapper objectMapper = new ObjectMapper();
    GetUserModel getUserModel = objectMapper.convertValue(parameterMap, GetUserModel.class);

    // ...
}

테스트

요청할 때 파라미터에 namephone을 입력하고 컨트롤러에서 모델의 멤버변수명으로 제대로 매핑되는지 확인합니다.

  • WebMvcTest를 사용합니다.
  • 위에서 말씀드렸던 것처럼 테스트에서는 요청한 파라미터 그대로 모델을 응답합니다.
  • MockMvcResultMatchers.jsonPath()를 이용해 응답 json의 값을 가져와 요청과 동일한지 확인합니다.
@ExtendWith(SpringExtension.class)
@WebMvcTest(UserController.class)
class UserControllerTest {

	@Autowired
	MockMvc mockMvc;

	@Test
	public void jackson_이용_테스트() throws Exception {
		String name = "Eddy";
		String phone = "010xxxxXXXX";

		MultiValueMap<String, String> requestParam = new LinkedMultiValueMap<>();
		requestParam.set("name", name);
		requestParam.set("phone", phone);

		mockMvc
				.perform(
						MockMvcRequestBuilders.get("/users/jackson")
								.params(requestParam)
				)
				.andDo(MockMvcResultHandlers.print())
				.andExpect(MockMvcResultMatchers.status().isOk())
				.andExpect(MockMvcResultMatchers.jsonPath("$.userName").value(name))
				.andExpect(MockMvcResultMatchers.jsonPath("$.userPhone").value(phone));
	}
}

image

방법 2. set 메소드 사용

모델 클래스에 원하는 이름의 set 메소드를 생성해줍니다.

@Getter
public class GetUserModelUsingSetter {

    private String userName;
    private String userPhone;
    // ...

    public void setName(String name) {
        this.userName = name;
    }

    public void setPhone(String phone) {
        this.userPhone = phone;
    }

    // ... setter
}

테스트

@ExtendWith(SpringExtension.class)
@WebMvcTest(UserSetterController.class)
class UserSetterControllerTest {

	@Autowired
	MockMvc mockMvc;

	@Test
	public void setter_이용_테스트() throws Exception {
		String name = "Eddy";
		String phone = "010xxxxXXXX";

		MultiValueMap<String, String> requestParam = new LinkedMultiValueMap<>();
		requestParam.set("name", name);
		requestParam.set("phone", phone);

		mockMvc
				.perform(
						MockMvcRequestBuilders.get("/users/setter")
								.params(requestParam)
				)
				.andDo(MockMvcResultHandlers.print())
				.andExpect(MockMvcResultMatchers.status().isOk())
				.andExpect(MockMvcResultMatchers.jsonPath("$.userName").value(name))
				.andExpect(MockMvcResultMatchers.jsonPath("$.userPhone").value(phone));
	}
}

image

방법 3. 생성자 사용

생성자의 파라미터 변수명을 요청받을 이름과 같이 해줍니다.

@Getter
public class GetUserModelUsingConstructor {

    private String userName;
    private String userPhone;
    // ...

    public GetUserModelUsingConstructor(String name, String phone, /* ... */) {
        this.userName = name;
        this.userPhone = phone;
        // ...
    }
}

테스트

@ExtendWith(SpringExtension.class)
@WebMvcTest(UserConstructorController.class)
class UserConstructorControllerTest {

	@Autowired
	MockMvc mockMvc;

	@Test
	public void 생성자_이용_테스트() throws Exception {
		String name = "Eddy";
		String phone = "010xxxxXXXX";

		MultiValueMap<String, String> requestParam = new LinkedMultiValueMap<>();
		requestParam.set("name", name);
		requestParam.set("phone", phone);

		mockMvc
				.perform(
						MockMvcRequestBuilders.get("/users/constructor")
								.params(requestParam)
				)
				.andDo(MockMvcResultHandlers.print())
				.andExpect(MockMvcResultMatchers.status().isOk())
				.andExpect(MockMvcResultMatchers.jsonPath("$.userName").value(name))
				.andExpect(MockMvcResultMatchers.jsonPath("$.userPhone").value(phone));
	}
}

image

Tip!

setter와 생성자를 이용할 때 모델의 멤버 변수에 유효성 검사 어노테이션을 사용하고 컨트롤러의 파라미터에 @Valid 어노테이션을 사용하면 유효성 검사를 할 수 있습니다.

@Getter
public class GetUserModelUsingSetter {

    @NotEmpty // 유효성 검사
    private String userName;
    private String userPhone;
    // ...

    public void setName(String name) {
        this.userName = name;
    }

    public void setPhone(String phone) {
        this.userPhone = phone;
    }

    // ... setter
}
@GetMapping("/users")
public Page<User> getUsers(@Valid GetUserModelUsingSetter getUserModel) {
    // ...
}

댓글남기기