@RequestParam, @PathVariable, @RequestBody의 차이를 설명해주세요.
힌트
URL 쿼리스트링, URL 경로 변수, HTTP Body에서 각각 데이터를 바인딩합니다.
정답 및 해설
@RequestParam, @PathVariable, @RequestBody의 차이를 설명해주세요.
Spring MVC에서 클라이언트의 HTTP 요청 데이터를 컨트롤러 메서드의 파라미터로 바인딩하는 방법은 데이터가 어디에 위치하느냐에 따라 달라집니다. @RequestParam, @PathVariable, @RequestBody는 각각 쿼리 파라미터, URL 경로 변수, 요청 본문에서 데이터를 추출합니다. 이 차이를 명확히 이해하면 RESTful API를 올바르게 설계할 수 있습니다.
@RequestParam
개요
@RequestParam은 URL 쿼리 파라미터(Query String)를 컨트롤러 메서드의 파라미터에 바인딩합니다.
GET /users?page=1&size=20&keyword=spring
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
쿼리 파라미터 영역
기본 사용법
@RestController
@RequestMapping("/users")
public class UserController {
// GET /users?page=1&size=20
@GetMapping
public Page<UserDto> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return userService.findAll(page, size);
}
// GET /users/search?keyword=spring&active=true
@GetMapping("/search")
public List<UserDto> searchUsers(
@RequestParam String keyword,
@RequestParam(required = false) Boolean active) {
return userService.search(keyword, active);
}
}
주요 속성
@RequestParam(
value = "q", // 파라미터 이름 (생략 시 변수명 사용)
required = false, // 필수 여부 (기본값: true)
defaultValue = "" // 기본값 (required=false와 함께 사용)
)
String keyword
Map으로 받기
파라미터 이름을 미리 알 수 없을 때 Map으로 전체를 수신할 수 있습니다.
// GET /search?name=John&age=30&city=Seoul
@GetMapping("/search")
public List<UserDto> dynamicSearch(@RequestParam Map<String, String> params) {
// params = {"name": "John", "age": "30", "city": "Seoul"}
return userService.dynamicSearch(params);
}
여러 값 받기 (다중 값)
// GET /items?ids=1&ids=2&ids=3
@GetMapping("/items")
public List<ItemDto> getItems(@RequestParam List<Long> ids) {
return itemService.findByIds(ids);
}
@PathVariable
개요
@PathVariable은 **URL 경로(Path)**의 일부를 변수로 추출합니다. RESTful API에서 리소스를 식별하는 데 주로 사용됩니다.
GET /users/42/orders/7
^^ ^
userId=42 orderId=7
기본 사용법
@RestController
@RequestMapping("/users")
public class UserController {
// GET /users/42
@GetMapping("/{id}")
public UserDto getUser(@PathVariable Long id) {
return userService.findById(id);
}
// GET /users/42/orders/7
@GetMapping("/{userId}/orders/{orderId}")
public OrderDto getUserOrder(
@PathVariable Long userId,
@PathVariable Long orderId) {
return orderService.findByUserIdAndOrderId(userId, orderId);
}
}
변수명 불일치 시
URL 템플릿 변수명과 파라미터명이 다를 경우 value 속성으로 명시합니다.
// GET /users/42
@GetMapping("/{user-id}")
public UserDto getUser(@PathVariable("user-id") Long userId) {
return userService.findById(userId);
}
required 속성
// 선택적 경로 변수 (Spring 4.3.3+)
@GetMapping({"/users", "/users/{id}"})
public Object getUsers(@PathVariable(required = false) Long id) {
if (id == null) {
return userService.findAll();
}
return userService.findById(id);
}
@RequestBody
개요
@RequestBody는 HTTP 요청의 **본문(Body)**을 Java 객체로 역직렬화합니다. 주로 Content-Type: application/json인 POST, PUT, PATCH 요청에서 사용됩니다. 내부적으로 HttpMessageConverter가 JSON → Java 객체 변환을 담당합니다.
POST /users
Content-Type: application/json
{
"name": "홍길동",
"email": "hong@example.com",
"age": 30
}
기본 사용법
// Request DTO
public class CreateUserRequest {
@NotBlank
private String name;
@Email
private String email;
@Min(1)
private int age;
// getter/setter 또는 @Data (Lombok)
}
@RestController
@RequestMapping("/users")
public class UserController {
// POST /users
@PostMapping
public ResponseEntity<UserDto> createUser(
@Valid @RequestBody CreateUserRequest request) {
UserDto created = userService.create(request);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(created);
}
// PUT /users/42
@PutMapping("/{id}")
public UserDto updateUser(
@PathVariable Long id,
@RequestBody UpdateUserRequest request) {
return userService.update(id, request);
}
}
@Valid와 함께 사용
public class CreateUserRequest {
@NotBlank(message = "이름은 필수입니다")
private String name;
@Email(message = "올바른 이메일 형식이어야 합니다")
@NotNull
private String email;
@Min(value = 1, message = "나이는 1 이상이어야 합니다")
@Max(value = 150, message = "나이는 150 이하이어야 합니다")
private int age;
}
@PostMapping
public ResponseEntity<UserDto> createUser(
@Valid @RequestBody CreateUserRequest request,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
throw new ValidationException(bindingResult);
}
return ResponseEntity.ok(userService.create(request));
}
@RequestHeader
HTTP 헤더 값을 파라미터로 바인딩합니다.
@GetMapping("/protected")
public ResponseEntity<String> protectedEndpoint(
@RequestHeader("Authorization") String authHeader,
@RequestHeader(value = "Accept-Language", defaultValue = "ko") String language) {
// authHeader = "Bearer eyJhbGciOiJIUzI1NiJ9..."
// language = "ko"
return ResponseEntity.ok("접근 성공");
}
@ModelAttribute
폼 데이터(application/x-www-form-urlencoded) 또는 쿼리 파라미터를 객체에 한 번에 바인딩합니다.
public class SearchFilter {
private String keyword;
private String category;
private int minPrice;
private int maxPrice;
// getter/setter
}
// GET /products?keyword=노트북&category=IT&minPrice=100000&maxPrice=2000000
@GetMapping("/products")
public List<ProductDto> searchProducts(@ModelAttribute SearchFilter filter) {
// filter.keyword = "노트북"
// filter.category = "IT"
// filter.minPrice = 100000
return productService.search(filter);
}
@ModelAttribute는 @RequestParam을 하나씩 받는 것과 달리, 여러 파라미터를 하나의 객체로 묶어서 받을 수 있어 편리합니다.
비교 요약
데이터 위치별 어노테이션 선택
HTTP 요청
├── URL 경로: GET /users/42 → @PathVariable
├── 쿼리 파라미터: GET /users?page=1 → @RequestParam
├── 요청 본문: POST /users (JSON) → @RequestBody
├── 요청 헤더: Authorization: Bearer → @RequestHeader
└── 폼 데이터: application/x-www-... → @ModelAttribute
실전 REST API 설계 예시
@RestController
@RequestMapping("/api/v1")
public class ProductController {
// 단일 상품 조회: 경로 변수로 ID 전달
// GET /api/v1/products/123
@GetMapping("/products/{id}")
public ProductDto getProduct(@PathVariable Long id) {
return productService.findById(id);
}
// 상품 목록 검색: 쿼리 파라미터로 필터/페이징 전달
// GET /api/v1/products?keyword=노트북&page=0&size=20
@GetMapping("/products")
public Page<ProductDto> searchProducts(
@RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return productService.search(keyword, page, size);
}
// 상품 생성: 요청 본문으로 데이터 전달
// POST /api/v1/products
@PostMapping("/products")
public ResponseEntity<ProductDto> createProduct(
@Valid @RequestBody CreateProductRequest request,
@RequestHeader("X-User-Id") Long userId) {
ProductDto created = productService.create(request, userId);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
// 상품 수정: 경로 변수(ID) + 요청 본문(수정 내용)
// PUT /api/v1/products/123
@PutMapping("/products/{id}")
public ProductDto updateProduct(
@PathVariable Long id,
@RequestBody UpdateProductRequest request) {
return productService.update(id, request);
}
}
요약 표
| 어노테이션 | 데이터 위치 | HTTP 메서드 | Content-Type | 주요 용도 |
|---|---|---|---|---|
| @RequestParam | URL 쿼리 스트링 | 주로 GET | 무관 | 검색 필터, 페이징, 정렬 |
| @PathVariable | URL 경로 | GET/PUT/DELETE 등 | 무관 | 리소스 식별 (ID) |
| @RequestBody | HTTP 요청 본문 | POST/PUT/PATCH | application/json | 객체 생성/수정 |
| @RequestHeader | HTTP 헤더 | 모든 메서드 | 무관 | 인증 토큰, 언어 설정 |
| @ModelAttribute | 쿼리 파라미터/폼 | GET/POST | application/x-www-form-urlencoded | 폼 제출, 검색 조건 객체 |