오봉이와 함께하는 개발 블로그

스프링 MVC 1 - PRG(Post/Redirect/Get), RedirectAttribute 본문

BE/Spring

스프링 MVC 1 - PRG(Post/Redirect/Get), RedirectAttribute

오봉봉이 2022. 8. 15. 23:50
728x90

PRG Post/Redirect/Get

/**
* @ModelAttribute 자체 생략 가능
* model.addAttribute(item) 자동 추가
*  */
@PostMapping("/add")
public String addItemV4(Item item) {
    itemRepository.save(item);
    return "basic/item";
}

해당 컨트롤러는 심각한 문제가 있다.
등록을 완료하고 새로고침을 클릭하면 계속해서 중복 등록되는 것을 확인할 수 있다.

전체 흐름

  1. 상품 목록에서 상품 등록 폼으로 이동
  2. 등록 폼에서 저장을 누르면 저장이 되며 상품 저장 컨트롤러가 상품 상세 뷰를 호출
  3. URL은 상품 저장 URL이 남아 있다.
  4. 새로고침 하면 상품 저장 URL이 그대로 입력됨
  5. 중복 등록

POST 등록 후 새로 고침

  1. 클라이언트가 상품 등록 폼 요청
  2. 서버는 응답해서 상품 등록 폼을 보여줌
  3. 폼에 값 입력
  4. 상품 등록 버튼 클릭
  5. POST로 서버에 넘어가서 상품 저장 컨트롤러가 받아서 로직 처리
  6. 컨트롤러의 로직이 상품 상세를 반환하기 때문에 상품 상세를 보여줌
  7. 하지만 URL은 상품 저장 URL 그대로기 때문에 새로고침을 누르면 전에 3번 4번이 생략되며 값은 유지되고, 5번 6번이 그대로 다시 요청됨

이 문제를 어떻게 해결할 수 있을까?

POST, Redirect GET

웹 브라우저의 새로 고침은 마지막에 서버에 전송한 데이터를 다시 전송한다.
상품 저장이 이뤄지면 반환 값을 Redirect를 통해 브라우저 입장에서 서버에 뷰를 새로 요청하도록 하는 방법을 통해 해결할 수 있다.
마지막에 서버에 뷰를 새로 요청해서 받은 것이 상품 상세에 대한 뷰이기 때문에 새로고침을 해도 상품 상세에 대한 뷰를 요청하게 된다.

@PostMapping("/add")
public String addItemV5(@ModelAttribute("item") Item item,
                        Model model) {
    itemRepository.save(item);
    model.addAttribute("item", item);

    return "redirect:/basic/items/" + item.getId();
}

주의

"redirect:/basic/items/" + item.getId() redirect에서 +item.getId()처럼 URL에 변수를 더해서 사용하는 것은 URL 인코딩이 안되기 때문에 위험하다.
RedirectAttributes를 사용하자.

RedirectAttributes

BasicItemController에 추가

/**
* RedirectAttributes
*/
@PostMapping("/add")
public String addItemV6(@ModelAttribute("item") Item item, Model model, RedirectAttributes redirectAttributes) {
    Item savedItem = itemRepository.save(item);
    model.addAttribute("item", item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/basic/items/{itemId}";
}

redirectAttributes.addAttribute("itemId", savedItem.getId());를 통해서 반환할 때 값을 사용할 수 있게 되었다.
return "redirect:/basic/items/{itemId}";

savedItem.getId()를 문자열 itemId라고 명시하고 반환값에서 {itemId}라고 사용했다. 결과적으로 보면 PathVariable의 효과를 얻었다.

redirectAttributes.addAttribute("status", true);는 해당 컨트롤러에서 사용하지 않았기 때문에 쿼리 파라미터로 처리된다.

실제 값을 추가하고 redirect된 링크를 보면 아래와 같다.
http://localhost:8080/basic/items/3?status=true

출처 : 인프런 김영한 지식 공유자님 강의 - 스프링 MVC 1편 백엔드 웹 개발 핵심 기술
728x90
Comments