@PathVariable vs @RequestParam 완벽 가이드
by 브이담곰Spring Framework에서 HTTP 요청의 데이터를 받는 두 가지 주요 어노테이션인 @PathVariable
과 @RequestParam
의 차이점과 사용법을 알아보자.
📍 @PathVariable
URL 경로의 일부를 변수로 추출하는 어노테이션
기본 문법
@GetMapping("/users/{id}")
public String getUser(@PathVariable Long id) {
// id는 URL 경로에서 추출됨
}
특징
- URL 경로 자체가 데이터
- RESTful API 설계에 적합
- 필수 데이터 (경로의 일부이므로 생략 불가)
- 보통 리소스 식별자로 사용
사용 예시
1. 기본 사용법
@Controller
@RequestMapping("/menu")
public class MenuController {
// URL: /menu/123
@GetMapping("/{id}")
public String getMenu(@PathVariable Long id, Model model) {
MenuDTO menu = menuService.findById(id);
model.addAttribute("menu", menu);
return "menu/detail";
}
// URL: /menu/delete/456
@PostMapping("/delete/{menuId}")
public String deleteMenu(@PathVariable("menuId") Long id) {
menuService.deleteMenu(id);
return "redirect:/menu/list";
}
}
2. 여러 개의 PathVariable
// URL: /users/123/orders/456
@GetMapping("/users/{userId}/orders/{orderId}")
public String getUserOrder(@PathVariable Long userId,
@PathVariable Long orderId,
Model model) {
OrderDTO order = orderService.findByUserAndOrder(userId, orderId);
model.addAttribute("order", order);
return "order/detail";
}
3. Optional PathVariable
// URL: /products 또는 /products/electronics
@GetMapping({"/products", "/products/{category}"})
public String getProducts(@PathVariable(required = false) String category,
Model model) {
if (category != null) {
// 카테고리별 상품 조회
model.addAttribute("products", productService.findByCategory(category));
} else {
// 전체 상품 조회
model.addAttribute("products", productService.findAll());
}
return "product/list";
}
🔗 @RequestParam
URL의 쿼리 파라미터나 폼 데이터를 추출하는 어노테이션.
기본 문법
@GetMapping("/search")
public String search(@RequestParam String keyword) {
// ?keyword=값 에서 값을 추출
}
특징
- URL 뒤에 ?key=value 형태
- 선택적 파라미터 (기본값 설정 가능)
- 필터링, 검색, 페이징에 주로 사용
- 폼 데이터도 받을 수 있음
사용 예시
1. 기본 사용법
@Controller
@RequestMapping("/menu")
public class MenuController {
// URL: /menu/search?keyword=햄버거
@GetMapping("/search")
public String searchMenu(@RequestParam String keyword, Model model) {
List<MenuDTO> menus = menuService.searchByKeyword(keyword);
model.addAttribute("menus", menus);
return "menu/search-result";
}
// URL: /menu/list?page=1&size=10
@GetMapping("/list")
public String getMenuList(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
Model model) {
PageResult<MenuDTO> result = menuService.findMenus(page, size);
model.addAttribute("menus", result);
return "menu/list";
}
}
2. 선택적 파라미터와 기본값
// URL: /products?category=electronics&minPrice=100&maxPrice=500
@GetMapping("/products")
public String getProducts(@RequestParam(required = false) String category,
@RequestParam(defaultValue = "0") int minPrice,
@RequestParam(defaultValue = "999999") int maxPrice,
@RequestParam(defaultValue = "name") String sortBy,
Model model) {
ProductSearchCriteria criteria = ProductSearchCriteria.builder()
.category(category)
.minPrice(minPrice)
.maxPrice(maxPrice)
.sortBy(sortBy)
.build();
List<ProductDTO> products = productService.search(criteria);
model.addAttribute("products", products);
return "product/list";
}
3. 폼 데이터 받기
// HTML 폼에서 POST 요청
@PostMapping("/register")
public String registerMenu(@RequestParam String name,
@RequestParam int price,
@RequestParam String category,
@RequestParam(defaultValue = "Y") String status) {
MenuDTO menu = MenuDTO.builder()
.name(name)
.price(price)
.category(category)
.status(status)
.build();
menuService.register(menu);
return "redirect:/menu/list";
}
4. 배열/리스트 파라미터
// URL: /menu/delete?ids=1&ids=2&ids=3
@PostMapping("/delete")
public String deleteMenus(@RequestParam("ids") List<Long> menuIds) {
menuService.deleteMenus(menuIds);
return "redirect:/menu/list";
}
// URL: /products?tags=sale&tags=new&tags=popular
@GetMapping("/products")
public String getProductsByTags(@RequestParam List<String> tags, Model model) {
List<ProductDTO> products = productService.findByTags(tags);
model.addAttribute("products", products);
return "product/list";
}
⚖️ 언제 무엇을 사용할까?
@PathVariable 사용 시기
- 리소스 식별:
/users/{id}
,/orders/{orderId}
- 계층 구조:
/users/{userId}/orders/{orderId}
- RESTful API: CRUD 작업의 대상 지정
- 필수 데이터: 반드시 있어야 하는 값
// ✅ 좋은 예시
@GetMapping("/users/{id}") // 특정 사용자 조회
@PutMapping("/products/{id}") // 특정 상품 수정
@DeleteMapping("/orders/{id}") // 특정 주문 삭제
@GetMapping("/categories/{categoryId}/products/{productId}") // 계층 구조
@RequestParam 사용 시기
- 검색/필터링: 검색어, 필터 조건
- 페이징: page, size, sort
- 선택적 옵션: 기본값이 있는 설정들
- 폼 데이터: 사용자 입력 데이터
// ✅ 좋은 예시
@GetMapping("/products?category=electronics&minPrice=100") // 필터링
@GetMapping("/users?page=1&size=20&sort=name") // 페이징
@GetMapping("/search?keyword=맛있는&type=korean") // 검색
@PostMapping("/contact") // 폼 데이터 // 폼 전송
🔄 혼합 사용 예시
실제 프로젝트에서는 두 어노테이션을 함께 사용하는 경우가 많다.
@Controller
@RequestMapping("/api/v1")
public class ProductController {
// 특정 카테고리의 상품들을 페이징과 정렬로 조회
// URL: /api/v1/categories/electronics/products?page=1&size=20&sort=price&order=asc
@GetMapping("/categories/{categoryId}/products")
public ResponseEntity<PageResult<ProductDTO>> getCategoryProducts(
@PathVariable Long categoryId, // 카테고리 ID (필수)
@RequestParam(defaultValue = "1") int page, // 페이지 번호 (선택)
@RequestParam(defaultValue = "20") int size, // 페이지 크기 (선택)
@RequestParam(defaultValue = "name") String sort, // 정렬 기준 (선택)
@RequestParam(defaultValue = "asc") String order // 정렬 순서 (선택)
) {
PageResult<ProductDTO> result = productService.findByCategoryId(
categoryId, page, size, sort, order
);
return ResponseEntity.ok(result);
}
// 특정 사용자의 주문들을 상태별로 필터링
// URL: /api/v1/users/123/orders?status=PENDING&startDate=2024-01-01&endDate=2024-12-31
@GetMapping("/users/{userId}/orders")
public ResponseEntity<List<OrderDTO>> getUserOrders(
@PathVariable Long userId, // 사용자 ID (필수)
@RequestParam(required = false) String status, // 주문 상태 (선택)
@RequestParam(required = false) String startDate, // 시작 날짜 (선택)
@RequestParam(required = false) String endDate // 종료 날짜 (선택)
) {
OrderSearchCriteria criteria = OrderSearchCriteria.builder()
.userId(userId)
.status(status)
.startDate(startDate)
.endDate(endDate)
.build();
List<OrderDTO> orders = orderService.searchOrders(criteria);
return ResponseEntity.ok(orders);
}
}
📋 요약 비교표
구분 | @PathVariable | @RequestParam |
---|---|---|
위치 | URL 경로 안 | URL 쿼리 파라미터 (?key=value) |
필수성 | 필수 (경로의 일부) | 선택적 (required 속성으로 제어) |
기본값 | 설정 불가 | defaultValue로 설정 가능 |
용도 | 리소스 식별, RESTful API | 검색, 필터링, 페이징, 폼 데이터 |
예시 URL | /users/123 |
/search?keyword=spring |
여러 값 | 여러 개 가능 | 배열/리스트로 받기 가능 |
🎯 베스트 프랙티스
1. RESTful API 설계
// ✅ 권장
@GetMapping("/users/{id}") // 사용자 조회
@GetMapping("/users/{id}/orders") // 사용자의 주문 목록
@GetMapping("/users?search=name&page=1") // 사용자 검색
// ❌ 비권장
@GetMapping("/getUser?id=123") // 비RESTful
@GetMapping("/user-orders?userId=123") // 계층 구조 미표현
2. 의미있는 변수명 사용
// ✅ 권장
@GetMapping("/products/{productId}")
public String getProduct(@PathVariable("productId") Long id) { }
// ✅ 권장 (변수명이 같으면 value 생략 가능)
@GetMapping("/products/{id}")
public String getProduct(@PathVariable Long id) { }
3. 유효성 검사 추가
@GetMapping("/users/{id}")
public String getUser(@PathVariable @Min(1) Long id) {
// id는 1 이상이어야 함
}
@GetMapping("/search")
public String search(@RequestParam @Size(min=2, max=50) String keyword) {
// 검색어는 2자 이상 50자 이하
}
이렇게 @PathVariable
과 @RequestParam
을 적절히 사용하면 깔끔하고 이해하기 쉬운 API를 만들 수 있다.
'Spring' 카테고리의 다른 글
REST API에서 페이징 처리가 필수인 이유와 실무 구현 방법 (1) | 2025.06.12 |
---|---|
Thymeleaf 문법 (4) | 2025.06.11 |
[Back-End] Bash에서 AWS EC2 접속 오류 해결 (0) | 2024.05.10 |
[Back-End] 우분투에 mongodb 안깔리는 오류 해결 (0) | 2024.05.10 |
[Front-End] (# 1) 웹 사이트 만들기 기본 틀 (0) | 2024.05.10 |
블로그의 정보
농담곰담곰이의곰담농
브이담곰