[Spring] PRG(Post, Redirect, Get)

strongmhk·2023년 8월 20일
0

Spring

목록 보기
25/25
post-thumbnail

문제상황



ItemController.java

	// class 레벨에 @RequestMapping("/basic/items")이 있다
    
    @GetMapping("/{itemId}")
    public String item(@PathVariable long itemId, Model model){
        Item item = itemRepository.findById(itemId);
        model.addAttribute("item", item);
        return "basic/item";

    }
    
    @PostMapping("/add")
    // @ModelAttribute의 이름을 생략하면 클래스 이름의 맨앞만 소문자로 바꿔 모델에 저장
    // @ModelAttribute HelloItem item -> model.addAttribute("helloItem", item);
    public String addItemV3(@ModelAttribute Item item){
        itemRepository.save(item);
        return "basic/item";
    }
    
    



template/basic/item.html

<!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"
           value="1" th:value="${item.id}" readonly>
  </div>
  <div>
    <label for="itemName">상품명</label>
    <input type="text" id="itemName" name="itemName" class="form-control"
           value="상품A" th:value="${item.itemName}" readonly>
  </div>
  <div>
    <label for="price">가격</label>
    <input type="text" id="price" name="price" class="form-control"
           value="10000" th:value="${item.price}" readonly>
  </div>
  <div>
    <label for="quantity">수량</label>
    <input type="text" id="quantity" name="quantity" class="form-control"
           value="10" 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>

위의 컨트롤러와 뷰 템플릿(thymeleaf) 파일을 보면, 상품을 상품 등록 폼을 통해 등록을 하면
http://localhost:8080/basic/items/add로 post 요청이 전송되고 ItemRepository에 상품이 등록된 후 템플릿 파일인 basic/item이 호출된다
그런데 여기에는 문제가 있다.




  1. 상품 등록 폼의 페이지를 get으로 요청하면 상품 등록 폼이 응답됨
  2. 상품 등록 폼에서 데이터를 입력하고 저장을 선택하면 POST /add + 상품 데이터가 서버로 전송, 그리고 템플릿 뷰 반환
  3. 반환된 뷰에서 새로고침을 누르면 POST /add + 상품 데이터가 서버로 다시 전송(마지막으로 전송한 데이터를 전송함)
  4. 새로고침시에 계속 상품이 생성됨






위의 url를 보면 /add이고 새로고침을 누르면 위와 같은 작업을 반복할 수 있다는 표시창이 뜬다
그렇다면 근본적인 이유가 무엇일까? 그것은 바로 url를 응답한게 아니고 템플릿 내부의 뷰를 응답했기 때문이다
@Controller는 return에 적은 이름의 논리이름을 뷰 리졸버를 통해 물리 이름으로 바꾸어 프로젝트 파일 내부의 template 폴더에서 리소스를 찾아 응답한다
즉, 이건 http url이 아니다
실제로 위에서 상품 등록 시 /add로 POST 요청을 보냈는데 응답된 뷰의 url도 동일하다
그렇기 때문에 새로고침시 계속 똑같은 요청이 반복되는 것이다
그럼 해결방법은? redirect를 통해 http url을 응답해주면 된다!







PRG,RedirectAttributes 활용

위 그림을 보면 Post 요청을 보낸 후 redirect를 통해 상품 상세 페이지를 get요청하는 url로 redirect해준다

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

컨트롤러를 다음과 같이 수정해준다
return에서 GET /basic/items/{itemId}으로 redirect 해준다
그러면 이제 응답된 상품 상세 페이지의 url이 바뀔 것이다!
return "redirect:/basic/items/" + item.getId();와 같이 id를 객체에서 직접 꺼내 보내면 url 인코딩과정에서 문제가 될 수 있기 때문에
redirectAttributes.addAttribute("itemId", savedItem.getId())를 사용해 redirect 모델에 값을 담아 응답해준다







잘 바뀌었다!
items/3/status=true로 잘 바뀌었다

위의 url에 쿼리파라미터로 status=true가 보이는데,
모델에 담으면 뷰에서 사용할 수 있다

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

다음과 같이 모델에 담아넘기면 param이라는 모델에 기본적으로 담긴다
프로퍼티 접근식으로 모델에 담아 넘긴 값을 조회해 활용할 수 있다!

profile
저 커서 개발자가 될래요!

0개의 댓글