본문 바로가기
Spring

[Spring] @RequestParam @ModelAttribute 로 요청 파라미터 받기

by jinjin98 2022. 8. 26.

스프링 프레임워크를 사용해 웹 어플리케이션을 개발하게 되면 MVC 패턴에서 Controller 로 

클라이언트의 요청 파라미터를 받게 되는데 이 때 사용하는 어노테이션인

 @RequestParam @ModelAttribute 를 알아보겠습니다,

 

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link th:href="@{/css/bootstrap.min.css}"
          href="../css/bootstrap.min.css" rel="stylesheet">
    <style>
        .container {
            max-width: 560px;
        }
    </style>
</head>
<body>

<div class="container">

    <div class="py-5 text-center">
        <h2>상품 등록 폼</h2>
    </div>

    <h4 class="mb-3">상품 입력</h4>

    <form th:action method="post">
        <div>
            <label for="itemName">상품명</label>
            <input type="text" id="itemName" name="itemName" class="form-control" placeholder="이름을 입력하세요">
        </div>
        <div>
            <label for="price">가격</label>
            <input type="text" id="price" name="price" class="form-control" placeholder="가격을 입력하세요">
        </div>
        <div>
            <label for="quantity">수량</label>
            <input type="text" id="quantity" name="quantity" class="form-control" placeholder="수량을 입력하세요">
        </div>

        <hr class="my-4">

        <div class="row">
            <div class="col">
                <button class="w-100 btn btn-primary btn-lg" type="submit">상품 등록</button>
            </div>
            <div class="col">
                <button class="w-100 btn btn-secondary btn-lg"
                        onclick="location.href='items.html'"
                        th:onclick="|location.href='@{/basic/items}'|"
                        type="button">취소</button>
            </div>
        </div>

    </form>

</div> <!-- /container -->
</body>
</html>

 

두 어노테이션의 기능을 알아보기 위한 예시입니다.

상품명, 가격, 수량을 입력하고 상품 등록 버튼을 누르면 버튼의 URL을 Controller가 받아 

입력한 요청 파라미터들을 처리 할 수 있습니다.

상품 등록 버튼을 누를 때 요청 URL은 form태그의 action에 적어줘야 하는데

현재 템플릿 엔진을 타임리프를 사용헤 th:action을 사용했고 th:action은 URL을 적어주지 않으면

현재 페이지인 뷰를 반환해준 Controller의 요청 URL을 사용합니다.

이 페이지로 들어올 때 URL은 basic/items/add에 GET 방식이고 

이 페이지에서 입력한 요청 파라미터를 처리하는 URL은 basic/items/add에 POST 방식입니다.

 

@Controller
@RequiredArgsConstructor
public class ItemController {

    private final ItemRepository itemRepository;

	//상품 등록 페이지로 가는
    @GetMapping("/basic/items/add")
    public String addForm() {
        return "basic/addForm";
    }

	//상품 등록 페이지에서 상품 등록 버튼을 누르면
    @PostMapping("/basic/items/add")
    public String addItem(@RequestParam("itemName") String itemName,
                          @RequestParam("price") int price,
                          @RequestParam(value = "quantity", required = false, defaultValue = "0")
                          Integer quantity,
                          Model model) {

        Item item = new Item();
        item.setItemName(itemName);
        item.setPrice(price);
        item.setQuantity(quantity);

        itemRepository.save(item);

        model.addAttribute("item", item);

        return "basic/item";
    }
}

 

@RequestParam 은 말 그대로 요청 파라미터를 받는데

@RequestParam 어노테이션 하나당 하나의 요청 파라미터를 받을 수 있습니다.

@RequestParam("itemName") 처럼 중괄호 안에 값을 받을 요청 파라미터 이름을

적고 옆에 해당 값에 맞는 타입의 필드를 생성해놓으면 그 필드에 값이 들어가게 됩니다.

이 떄 요청 파라미터 이름은 html의 input태그의 name값입니다.

또한 required 속성 값을 false로 줘서 파라미터 필수 여부를 지정할 수도 있습니다.

이렇게 설정하면 해당 필드에 요청 파라미터 값이 들어오지 않아도 에러가 발생하지 않습니다.

값이 안들어왔을 때를 생각해 defaultvalue 속성 값으로 기본값을 설정할 수도 있습니다.

 

현재 위의 코드는 상품 입력 페이지에서 상품의 이름, 가격, 수량을 적으면 3개의 요청 파라미터 값을

받고 상품 클래스로 객체를 만든 후에 해당 객체에 요청 파라미터 값을 넣어줍니다.

그리고 상품 객체를 저장소에 저장 후 모델에도 담고 반환 뷰 경로를 상품 상세 화면으로 적어줘

상품 상세 화면에서 상품의 정보를 볼 수 있게 해놨습니다.

 

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link th:href="@{/css/bootstrap.min.css}"
            href="../css/bootstrap.min.css" rel="stylesheet">
    <style>
        .container {
            max-width: 560px;
        }
    </style>
</head>
<body>

<div class="container">

    <div class="py-5 text-center">
        <h2>상품 상세</h2>
    </div>

    <div>
        <label for="itemId">상품 ID</label>
        <input type="text" id="itemId" name="itemId" class="form-control" th:value="${item.id}" readonly>
    </div>
    <div>
        <label for="itemName">상품명</label>
        <input type="text" id="itemName" name="itemName" class="form-control"  th:value="${item.itemName}" readonly>
    </div>
    <div>
        <label for="price">가격</label>
        <input type="text" id="price" name="price" class="form-control" th:value="${item.price}" readonly>
    </div>
    <div>
        <label for="quantity">수량</label>
        <input type="text" id="quantity" name="quantity" class="form-control"  th:value="${item.quantity}" readonly>
    </div>

    <hr class="my-4">

    <div class="row">
        <div class="col">
            <button class="w-100 btn btn-primary btn-lg"
                    onclick="location.href='editForm.html'"
                    th:onclick="|location.href='@{/basic/items/{itemId}/edit(itemId=${item.id})}'|"
                    type="button">상품 수정</button>
        </div>
        <div class="col">
            <button class="w-100 btn btn-secondary btn-lg"
                    onclick="location.href='items.html'"
                    th:onclick="|location.href='@{/basic/items}'|"
                    type="button">목록으로</button>
        </div>
    </div>

</div> <!-- /container -->
</body>
</html>

 

여기서 @RequestParam 의 기능을 이용해 코드를 줄일 수가 있는데요.

@RequestParam 은 요청 파라미터 값을 받는 필드 이름이 요청 파라미터 이름과 같으면 

() 중괄호 안의 요청 파라미터 이름을 생략 가능합니다.

위 코드에서는 요청 파라미터 이름과 필드 이름을 다 맞춰놓았기 때문에 모두 생략이 가능합니다.

이 뿐만이 아닙니다. required와 defaultvalue 속성 값을 설정할 필요가 없다면 

@RequestParam인 어노테이션 생략도 가능합니다.

 

@Controller
@RequiredArgsConstructor
public class ItemController {

    private final ItemRepository itemRepository;

	//상품 등록 페이지로 가는
    @GetMapping("/basic/items/add")
    public String addForm() {
        return "basic/addForm";
    }
    
	//상품 등록 페이지에서 상품 등록 버튼을 누르면
    @PostMapping("/basic/items/add")
    public String addItem(String itemName,
                          int price,
                          @RequestParam(required = false, defaultValue = "0")
                          Integer quantity, Model model) {

        Item item = new Item();
        item.setItemName(itemName);
        item.setPrice(price);
        item.setQuantity(quantity);

        itemRepository.save(item);

        model.addAttribute("item", item);

        return "basic/item";
    }
}

 

즉 이렇게 코드를 줄일 수가 있습니다.

여기서 코드를 더 줄일 수는 없을까?  하고 생각이 들 수 있는데 

이 때 사용하는게 @ModelAttribute 입니다.

 

@Controller
@RequiredArgsConstructor
public class ItemController {

    private final ItemRepository itemRepository;

	//상품 등록 페이지로 가는
    @GetMapping("/basic/items/add")
    public String addForm() {
        return "basic/addForm";
    }

	//상품 등록 페이지에서 상품 등록 버튼을 누르면
    @PostMapping("/basic/items/add")
    public String addItem(@ModelAttribute("item") Item item, Model model) {

        itemRepository.save(item);
        model.addAttribute("item", item); 
        
        return "basic/item";
    }
}

 

요청 파라미터를 한 개씩 일일히 받을 때마다 타입과 필드를 써줄 필요도 없고

객체를 생성해 요청 파라미터 값을 받은 필드를 넣어줄 필요도 없습니다.

@ModelAttribute를 써주고 옆에 객체를 생성해주면 요청 파라미터의 이름으로 

객체의 필드 이름을 찾고 해당 필드의 setter 메서드를 호출해 프로퍼티 접근법으로 요청 파라미터 값을 넣어줍니다.

 

이렇게 @ModelAttribute는 요청 파라미터를 받는 기능도 있지만 다른 기능도 있습니다.

바로 뷰에서 쓰이는 모델에 값을 추가해주는 기능입니다. 

모델 객체에 뷰에서 사용할 값의 이름과 뷰에서 사용할 값을 추가해주는

model.addAtribute("item", item) 와 같은 기능을 하는거죠, 

@ModelAttribute("item") 에서 중괄호 안의 값 "item" 은

model.addAtribute("item", item) 에서 "item" 과 같은 의미입니다.

만약 @ModelAttribute에서 값을 지정해주지 않으면 @ModelAttribute 옆에 있는 클래스 이름에서

앞글자만 소문자로 바꾼 이름으로 값이 자동으로 설정됩니다.

그래서 위 코드 처럼 item 이란 이름으로 설정할꺼면 그냥 @ModelAttribute만 써줘도 되고

이렇게 되면 @RequestParam 처럼 어노테이션 자체를 생략해도 기능이 동작합니다.

 

그래서 위 코드에서 최대한 코드를 줄이면

 

@Controller
@RequiredArgsConstructor
public class ItemController {

    private final ItemRepository itemRepository;

	//상품 등록 페이지로 가는
    @GetMapping("/basic/items/add")
    public String addForm() {
        return "basic/addForm";
    }

	//상품 등록 페이지에서 상품 등록 버튼을 누르면
    @PostMapping("/basic/items/add")
    public String addItem(Item item) {

        itemRepository.save(item);

        return "basic/item";
    }
}

 

이렇게 간단하게 만들 수 있습니다.

맨 처음에 @RequestParam 을 사용한 코드와 비교하면 코드 양이 눈에 띄게 줄은 걸 볼 수 있네요.

String 이나 int 같은 기본 타입을 받거나 요청 파라미터 개수가 적을 때는 @RequestParam 을 사용하고

요청 파라미터 개수가 많다면 파라미터들을 받을 클래스를

생성해 @ModelAttribute 를 사용하는게 효율적일거 같습니다.

댓글