스프링

[스프링 MVC 1편] PRG Post/Redirect/Get, RedirectAttributes

코딍코딍 2022. 6. 30. 21:22

PRG Post/Redirect/Get

심각한 문제를 가지는 구조

웹 브라우저의 새로 고침은 마지막에 서버에 전송한 데이터를 다시 전송한다.

상품 등록 폼에서 데이터를 입력하고 저장을 선택하면 POST /add + 상품 데이터를 서버로 전송한다.

이 상태에서 새로 고침을 또 선택하면 마지막에 전송한 POST /add + 상품 데이터를 서버로 다시 전송하게 된다.

그래서 내용은 같고, ID만 다른 상품 데이터가 계속 쌓이게 된다.

 

PRG패턴으로 해결한 구조

웹 브라우저의 새로 고침은 마지막에 서버에 전송한 데이터를 다시 전송한다.

새로 고침 문제를 해결하려면 상품 저장 후에 뷰 템플릿으로 이동하는 것이 아니라, 상품 상세 화면으로 리다이렉트를 호출해주면 된다.

웹 브라우저는 리다이렉트의 영향으로 상품 저장 후에 실제 상품 상세 화면으로 다시 이동한다. 따라서 마지막에 호출한 내용이 상품 상세 화면인 GET /items/{id} 가 되는 것이다.

이후 새로고침을 해도 상품 상세 화면으로 이동하게 되므로 새로 고침 문제를 해결할 수 있다.

 

BasicItemController 수정

@PostMapping("/add")
    public String addItemV2(@ModelAttribute("item") Item item) {
        itemRepository.save(item);
//      return "basic/item";
        return "redirect:/basic/items/" + item.getId();
    }
  • 리다이렉트는 뷰 템플릿을 호출하는 것이 아니라 경로를 호출하는 것이다.
    • re(다시)+direct(지시하다) => 브라우저에게 다른 URL을 지시할 수 있는 것
  • 그러므로  @GetMapping("/{itemId}") public String item(..., ...)이 호출된다.
  • 이런 문제 해결 방식을 PRG Post/Redirect/Get 이라 한다.

주의

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

 

 

RedirectAttributes

  • RedirectAttributes 를 사용하면 URL 인코딩도 해주고, pathVarible , 쿼리 파라미터까지 처리해준다.
  • redirect:/basic/items/{itemId} (아래 addItemV3 메서드 참조)
    • 경로 변수로 쓰이는 값은 pathVariable 바인딩: {itemId}
    • 경로 변수로 쓰이지 않는 값은 쿼리 파라미터로 처리: ?status=true

 


상품을 저장하고 상품 상세 화면으로 리다이렉트 한 것 까지는 좋으나 고객 입장에서 저장이 잘 된 것인지 안 된 것인지 확신이 들지 않는다. 그래서 RedirectAttributes를 사용해 저장이 잘 되었으면 상품 상세 화면에 "저장되었습니다"라는 메시지를 출력해보자.

BasicItemController 수정

@PostMapping("/add")
public String addItemV3(@ModelAttribute("item") Item item, RedirectAttributes redirectAttributes) {
    Item saveItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId",saveItem.getId());
    redirectAttributes.addAttribute("status",true);
    return "redirect:/basic/items/{itemId}";
}
  • 리다이렉트 결과 : http://localhost:8080/basic/items/3?status=true
  • 리다이렉트 할 때 간단히 status=true 를 추가하자. 그리고 뷰 템플릿에서 이 값이 있으면, 저장되었습니다. 라는 메시지를 출력한다.
  • saveItem.getId()도 인코딩 되었다.

 

뷰 템플릿 메시지 추가 (resources/templates/basic/item.html)

<h2 th:if="${param.status}" th:text="'저장 완료'"></h2>