사용자는 브라우저에 상품 등록 폼에서 값을 입력하고 POST 요청을 보낸다.
컨트롤러는 그 값을 받아 객체에 저장하고, 결과 화면을 보여주거나 상세 페이지로 이동한다.
content-type : application/x-www-form-urlencoded
메시지 바디에 쿼리 파라미터 형식으로 전잘된다. 예) usernamewow&age=100
@RequestParam
@RequestParam를 사용해서 하나씩 받아 수동으로 객체에 넣는다.
파라미터가 길어지면 코드도 길어지고 매번 set 해줘야하는 번거로움이 있다.
@PostMapping("/add")
public String addItemV1(@RequestParam String itemName,
@RequestParam int price,
@RequestParam 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
@ModelAttribute를 사용해서 객체를 생성하고, 폼에서 넘어온 데이터를 자동으로 객체에 바인딩된다.
그리고 해당 객체는 모델에도 자동으로 등록된다. (이름에 Model 있음) 그래서 model.addAttribute() 생략 가능!
@PostMapping("/add")
public String addItemV2(@ModelAttribute("item") Item item, Model model) {
//Model Item을 저장소에 저장
itemRepository.save(item);
//ModelAttribute는 Item 객체를 생성하고 model에 지정된 객체를 자동으로 넣어준다! 그래서 따로 model 담아서 보내줄 필요가 없다.
//model.addAttribute("item", item);
return "basic/item";
}
이름을 생략하면 클래스명의 첫글자만 소문자로 바껴 모델에 자동 등록된다.
@PostMapping("/add")
public String addItemV3(@ModelAttribute Item item) { //@ModelAttribute 이름 생략 가능
//생략하면 model에 저장되는 이름은 클래스명 첫글자만 소문자로 등록된다. Item -> item
itemRepository.save(item);
return "basic/item";
}
스프링은 Item처럼 객체 타입의 파라미터가 들어오면 자동으로 @ModelAttribute로 처리한다.
그래서 완전히 생략해도 동작됨
@PostMapping("/add")
public String addItemV4(Item item) {
//@ModelAttribute 자체도 생략 가능하다. 대상 객체는 모델에 자동으로 등록됨
itemRepository.save(item);
//뷰 호출
//웹 브라우저 입장에선 POST + 상품 데이터 전송이 마지막 호출이였음 새로고침하면 또 저장되는 문제!
return "basic/item";
}
🚨 문제 발생
새로 고침할 경우 데이터가 재등록되는 상황이다.
왜냐하면 사용자의 마지막 요청이 POST라서 새로고침하면 또 POST가 실행되는 것이다.
정상적으로 등록된 경우 상품 상세 페이지로 이동해야한다.
redirect
상품 상세 페이지로 리다이렉트 되기 때문에 브라우저 새로 고침해도 POST가 재전송되지 않는다. (GET이 마지막 요청)
하지만 문자열을 붙여 사용하면 URL 인코딩이 안 되어서 한글이나 특수문자 들어가면 위험하다.
@PostMapping("/add")
public String addItemV5(Item item) {
itemRepository.save(item);
//상품 저중 후에 뷰 템플릿이 아닌 상품 상세 화면으로 리다이렉트를 호출해준다.
//따라서 마지막에 호출한 내용이 상품 상세 화면인 GET이다.
//이렇게 더해서 사용하는 것은 URL 인코딩이 안 되기 때문에 위험하다! -> RedirectAttributes 사용할 것
return "redirect:/basic/items/" + item.getId();
}
RedirectAttributes
{itemId}는 자동으로 치환되고 나머지는 쿼리 파라미터로 붙는다.
그리고 URL 인코딩도 자동으로 처리된다.
//고객의 입장에선 안내문구가 없어서 정확히 저장됐는지 알 수 없기 때문에 안내문구 추가함
//redirectAttributes를 사용하면 URL 인코딩도 해주고 PathVariable, 쿼리 파라미터도 해준다.
@PostMapping("/add")
public String addItemV6(Item item, RedirectAttributes redirectAttributes) {
//저장된 결과
Item savedItem = itemRepository.save(item);
//redirectAttributes에 추가함
redirectAttributes.addAttribute("itemId", savedItem.getId());
//저장 확인을 위함
redirectAttributes.addAttribute("status", true);
//redirectAttributes에 넣은 id가 알아서 치환된다.
//남는 애들은 쿼리 파라미터로 추가되어 들어간다.
return "redirect:/basic/items/{itemId}";
//http://localhost:8080/basic/items/3?status=true
}
<!-- RedirectAttributes 추가 -->
<!-- param.status는 쿼리 파라미터를 편리하게 조회하는 기능이다. -->
<h2 th:if="${param.status}" th:text="'저장 완료'"></h2>
김영한, 스프링 MVC 1편
'🖥️ Back > Spring' 카테고리의 다른 글
김영한 스프링 MVC 2편 - 템플릿 (0) | 2025.06.30 |
---|---|
김영한 스프링 MVC 2편 - 타임리프 기본 기능 (0) | 2025.06.30 |
김영한 스프링 MVC 1편 - HTTP 메시지 컨버터 (0) | 2025.06.29 |
김영한 스프링 MVC 1편 - HTTP 응답 API, 메시지 바디에 직접 입력 (0) | 2025.06.29 |
김영한 스프링 MVC 1편 - HTTP 응답 정적 리소스, 뷰 템플릿 (0) | 2025.06.29 |