농담곰담곰이의곰담농

@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를 만들 수 있다.

블로그의 정보

농담곰담곰이의곰담농

브이담곰

활동하기