서버 측 매개변수 바인딩 메커니즘
Spring MVC 를 활용한 웹 애플리케이션 개발에서 컨트롤러 계층은 클라이언트로부터 전달된 데이터를 어떻게 받아들이느냐에 따라 비즈니스 로직 처리가 달라진다. 특히 요청 파라미터를 수집하고 변환하는 방식은 개발자의 의도에 따라 다르게 설계될 수 있다.
1. 동적 컬렉션 활용: @RequestParam Map
고정되지 않은 키 - 값 쌍을 유연하게 수용하기 위해 @RequestParam 어노테이션과 함께 Map<String, String> 타입의 변수를 선언할 수 있다. 이 방식은 별도의 자바빈 클래스 정의 없이도 다양한 출처의 데이터를 포착할 수 있게 한다.
- 쿼리 스트링: GET 요청 시 URL 뒤에 덧붙은
?key=value형태의 인자를 자동으로 추출한다. - 폼 엔코딩 데이터: POST 요청 시
Content-Type: application/x-www-form-urlencoded형식으로 전송된 폼 데이터 역시 동일한 맵 구조로 매핑된다. - AJAX 호출: JavaScript 라이브러리를 통해 폼 데이터 형태를 모방하여 보낸 패킷도 지원한다.
예를 들어, 여러 입력 필드가 포함된 필터 검색 기능을 구현할 때 이를 효과적으로 사용할 수 있다.
@GetMapping("/search/items")
public ApiResponse search(@RequestParam Map<String, String> filters) {
String category = filters.getOrDefault("category", "");
// 기타 필터 로직 처리
}
다만, 복수 값을 가지는 파라미터 (배열형) 에 대한 처리는 주의가 필요하다. Map 접근 시에는 보통 마지막 값만 반환되므로, 리스트 타입을 수신하려면 List<String> 또는 배열 타입으로 파라미터를 명시해야 한다. 또한, 복합 객체 (JSON 등) 는 이 방식으로 직접 파싱할 수 없으며 @RequestBody 를 사용해야 함을 기억해야 한다.
2. 객체 지향적 데이터 수신: DTO 와 폼 제출
구조화된 데이터를 다룰 때는 개별 파라미터보다는 전용 도메인 객체를 사용하는 것이 유지보수에 유리하다. HTML 폼 태그를 이용한 제출은 기본값이 POST 방식이며, 민감한 정보 보호와 대용량 전송 측면에서 권장된다.
클라이언트 측 폼 예시:
<form action="/secure/login" method="post">
<input type="text" name="id" placeholder="아이디">
<input type="password" name="pwd" placeholder="비밀번호">
<button type="submit">로그인</button>
</form>
서버 측에서는 해당 폼 필드명과 프로퍼티 명이 일치하는 경우 자동으로 객체에 세팅된다. 이때 @ModelAttribute 를 생략하더라도 이름 매칭 규칙에 의해 동작한다.
public class AuthRequest {
private String id;
private String pwd;
// getters, setters 필수
}
@PostMapping("/secure/login")
public String authenticate(AuthRequest authDto) {
log.info("Received login attempt for user: {}", authDto.getId());
return "loginSuccess";
}
3. 비동기 통신 및 JSON payload 처리
단순 폼 데이터가 아닌 복잡한 구조의 데이터를 주고받을 때는 JSON 형식을 채택하며, 이때는 반드시 Content-Type: application/json 헤더 설정과 @RequestBody 어노테이션 사용이 필수적이다.
자바스크립트 측 AJAX 설정:
$().ajax({
url: '/api/member/update',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
memberId: currentUserId,
nickname: newNickname
}),
success: function(resp) {
console.log('Update completed');
}
});
백엔드 인터페이스 정의:
public class UpdateProfileCommand {
private Long memberId;
private String nickname;
// Lombok @Data 권장
}
@PostMapping("/api/member/update")
public ApiResponse updateProfile(@RequestBody UpdateProfileCommand command) {
// 서비스 레이어 로직 실행
return ApiResponse.ok();
}
여기서 중요한 점은 프론트엔드에서 JSON 을 문자열로 직렬화 (`stringify`) 하여 보내야 하고, 백엔드는 이를 역직렬화하여 객체로 변환한다는 것이다.
4. 수치 타입의 호환성 문제
자바의 long 타입 데이터를 JSON 으로 직렬화하여 클라이언트로 보낼 때, 자바스크립트 엔진은 이를 일반 숫자 (Number) 타입으로 인식한다. 일반적인 범위 내의 정수는 정확한 비교 연산이 가능하지만, 매우 큰 정수 (BigInt 영역) 에서는 정밀도 손실 위험이 있으므로 고려해야 한다.
@GetMapping("/stock/{itemId}")
@ResponseBody
public ResponseEntity<ApiResponse> checkStock(@PathVariable Long itemId) {
Long stockCount = inventoryService.count(itemId);
return ResponseEntity.ok(ApiResponse.success(stockCount));
}
클라이언트 측에서는 반환된 값을 조건 분기로 처리할 수 있다.
success: function(data) {
if (data.value > 0) {
ui.showAvailability("Available");
} else {
ui.showAvailability("Out of Stock");
}
}
5. 요청 방식에 따른 데이터 위치 변별
AJAX 라이브러리인 jQuery 의 .ajax() 함수는 전송 모드에 따라 데이터의 물리적 위치를 자동으로 결정한다.
- GET 요청:
data속성에 지정된 객체는 URL 쿼리 문자열 (?a=b&c=d) 로 변환되어 리소스 경로 뒤에 붙는다. 이때 컨트롤러의 단순 소수 타입 파라미터는 별도의 어노테이션 없이도 바인딩된다. - POST 요청: 기본 설정에서는
application/x-www-form-urlencoded형태로 Request Body 에 포함된다. 역시 단순 타입 파라미터는 명시적인@RequestParam없이도 처리 가능하다. 하지만 명확성을 위해 어노테이션 사용을 권장한다.
따라서 개발자는 요청 메소드가 데이터를 어디에 담는지 (URL vs Body) 인지하고 있어야 하며, 이에 맞는 어노테이션 선택이 중요하다.
// 명시적 바인딩 예시
@GetMapping("/filter")
public Result filterItems(@RequestParam("page") int pageNum,
@RequestParam(required=false) String sortBy) {
// ...
}
결론적으로 @RequestParam 은 URL 파라미터나 폼 데이터 (x-www-form-urlencoded) 전용이며, JSON 바디를 처리하고자 한다면 무조건 @RequestBody 를 적용해야 오류 없이 통신이 보장된다.