인프런/스프링 MVC 1편

끝)리다이렉트(RedirectAttributes),PRG 패턴(Post/Redirect/Get)

backend dev 2023. 1. 30.

상품 수정 폼 컨트롤러

BasicItemController에 추가

 

상품 수정폼 이동 메소드

@GetMapping("/{itemId}/edit") //상품수정 폼으로 이동
public String editForm(@PathVariable Long itemId, Model model) { //수정하는 화면에서 상품내용이 보여야하므로 상품아이디를 받아 상품을 찾는다.
    Item item = itemRepository.findById(itemId);
    model.addAttribute("item", item);
    return "basic/editForm";
}
<form action="item.html" th:action method="post">
  <div>
    <label for="id">상품 ID</label>
    <input type="text" id="id" name="id" class="form-control" value="1" th:value="${item.id}"
           readonly>
  </div>
  <div>
    <label for="itemName">상품명</label>
    <input type="text" id="itemName" name="itemName" class="formcontrol" th:value="${item.itemName}"
           value="상품A">
  </div>
  <div>
    <label for="price">가격</label>
    <input type="text" id="price" name="price" class="form-control" th:value="${item.price}"
           value="10000">
  </div>
  <div>
    <label for="quantity">수량</label>
    <input type="text" id="quantity" name="quantity" class="formcontrol" th:value="${item.quantity}"
           value="10">
  </div>

상품 수정폼 이동url과 내용수정하는 url를 method만 다르게해서 구성할것이다.

그래서 th:action을 아무것도 주지않아서 현재 url 그대로에 post방식으로 데이터를 보낼것이고 

상품 수정폼안에 데이터는 상품 수정폼 이동메소드에서 모델에 담은 값을 이용해서 처리한다.

 

<button class="w-100 btn btn-secondary btn-lg"
        onclick="location.href='item.html'"
        th:onclick="|location.href='@{/basic/items/{itemId}(itemId=${item.id})}'|"
        type="button">취소</button>

취소버튼을 누르면 다시 상세페이지로 가게끔 설정해둔다. 

수정 로직

@PostMapping("/{itemId}/edit") //수정한 내용 저장하는 메소드, url은 같게 ,Method는 다르게 구성한다.
public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {
    itemRepository.update(itemId, item);
    return "redirect:/basic/items/{itemId}"; //리다이렉트를 통해 url경로가 바뀌게끔 처리할것이다.
}

 

( 컨트롤러에 매핑된 경로변수의 값은 redirect에도 사용할수 있다 !)

리다이렉트를 통해 다시한번 url호출을 하게되는것

https://keeeeeepgoing.tistory.com/163

 

리다이렉트를 사용했을때 결과

 

 

리다이렉트를 사용하지않는다면?

    @PostMapping("/{itemId}/edit") //수정한 내용 저장하는 메소드, url은 같게 ,Method는 다르게 구성한다.
    public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {
        itemRepository.update(itemId, item);
//        return "redirect:/basic/items/{itemId}"; //리다이렉트를 통해 url경로가 바뀌게끔 처리할것이다.
        return "/basic/item";
    }

/basic/item 템플릿에 필요한 데이터는 item객체 하나이다

@ModelAttribute로 인해 알아서 item이라는 속성이름으로 아이템객체하나가 담겨졌을것이다.

값을 55로 바꾸고 저장을 누르면

상품상세 페이지로 이동하면서 수량이 바뀐것을 확인할 수 있다.

하지만 url주소는 상품수정폼 그대로 인것을 확인 할 수 있다.

리다이렉트로 다시 한번 url를 호출하는것이 아닌

내부에서 뷰를 렌더링한후 forward()를 진행하므로 url이 바뀌지않는것을 확인할 수 있다.

 


PRG 패턴 (PRG -> [Post/Redirect/Get])

PRG는 post,redirect,get의 앞글자만 따온것

    @PostMapping("/add")
    public String addItemV4(@ModelAttribute Item item) {
        itemRepository.save(item);
        return "basic/item";
    }

    @PostMapping("/add")
    public String addItemV5(Item item) {
        itemRepository.save(item);
        return "basic/item";
    }

위의 코드가 상품등록이고, 등록후 새로고침을 누르니 계속 반복되서 등록되는것을 확인할 수 있었다.

상품등록폼에서 상품저장을 누르면 내부호출로 인해(forward()) url의 변경없이 상품상세 뷰가 보이게 된다.

그때 새로고침을 누른다면 url은 그대로 상품등록 url이고 데이터또한 그대로 담겨있다.

상품상세로 넘어갔지만 아직 url은 상품등록 url이고 payload까지 그대로인것을 확인할 수 있다. (post 인것도 그대로)

 

POST등록 후 새로고침

1. 클라이언트가 get으로 상품등록폼 뷰를 받는다.

2. 클라이언트가 상품등록버튼을 눌러 POST방식으로 상품저장 컨트롤러를 호출한다.

3. 상품저장한후 상품상세뷰를 보여준다.

4. 클라이언트가 새로고침을 하면 가장 최근에 한 행위를 다시한다 ( POST로 상품등록)

5. 그러면 새로고침하면 내용은 같고 ID만 다른 상품이 계속 저장된다.

 

POST -> redirect Get

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

그냥 뷰를 보여주는 GET방식의 컨트롤러로 리다이렉트를 해버리면 클라이언트가 아무리 새로고침을 해도 데이터가 저장,수정,삭제되는 경우는 없을것이다.

그래서 Post Redirect Get -> PRG 패턴!

포스트방식은 GET을 리다이렉트 해줘야한다.

@PostMapping("/add")
public String addItemV5(Item item) {
    itemRepository.save(item);
    return "redirect:/basic/items/" + item.getId();
}

다음과 같이 수정 ( 변수를 더해주는 방식으로 URL을 만들면 위험하다! ) 밑에서 최적의 방식으로 수정할것이다.

상품등록을 눌렀을때 확인해보면, Request는 post로 잘 동작했고 리다이렉션 응답코드는 302 상태코드이고

응답헤더의 Location을 보면 상품상세페이지로 가도록 되어있는것 확인할 수 있다.

 

 

리다이렉트 URL을 적을 때 유의할점

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

그냥 basic/items라고 적으면 지금 서버의 url + basic/items가 되버린다.

/basic/items라고 적어야 서버주소 + /basic/items가 된다 ! 주의


RedirectAttributes

@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}"; // RedirectAttributes에 넣은 속성값으로 치환이 된다, 들어가지않고 남은 속성값은 쿼리파라미터형식으로 url에 들어간다.
}

redirect에 {속성이름} 으로 적은 부분이 RedirectAttributes에 넣은 속성값으로 치환이 된다.

들어가지않고 남은 속성값은 쿼리파라미터형식으로 url에 들어간다. ( ?status=true 이런식으로)

(즉, RedirectAttributes를 사용하면 URL 인코딩도 해주고, pathvariable,쿼리파라미터까지 처리해준다.)

잘 동작한다.

뷰 템플릿 메시지 추가

RedirectAttributes를 통해 ?status=true라는 쿼리파라미터를 붙여서 리다이렉트 하게끔했는데

그때 리다이렉트로 호출하는 뷰 템플릿으로 가서 status가 true일때 메시지를 띄우게끔 수정해보자.

return "redirect:/basic/items/{itemId}";

위의 url을 호출하게 되면 상품상세 컨트롤러가 동작하므로 ,

@GetMapping("/{itemId}") //아이템 상세보기
public String item(@PathVariable long itemId, Model model) {
    Item item = itemRepository.findById(itemId);
    model.addAttribute("item", item);
    return "basic/item";
}

상품상세 뷰 템플릿(basic/item이 위치)을 가서 수정하면된다.

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

if를 이용해서 조건을 만들어준다. param을 가지고 파라미터값을 조회할수있다. 파라미터값 status가 true라면

동작하는 조건 태그이다.  text에 작은따음표를 이용해서 문자열을 넣어줘야한다고한다. 

저장완료가 잘 뜨는것을 확인할 수 있따.

그냥 상품목록에서 들어가면 해당 글자가 보이지않는다(파라미터를 이용한 조건문 때문에)

 

결론적으로 밑의 두형식이 많이 쓰일것같다.

@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}";
}
@PostMapping("/add")
public String addItemV6(@ModelAttribute Item item, RedirectAttributes redirectAttributes) {
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/basic/items/{itemId}"; 
}

리다이렉트를 하기위해 url를 인코딩해야할것이고, 그때 RedirectAttributes를 사용할것이다.

또 추가적으로 url에 pathVariable이나 쿼리스트링도 붙여서 뷰 템플릿에서 사용할수도 있고.

 

그리고 데이터를 객체로 변환해주고, 모델에 알아서 속성으로 담아주는(속성이름은 클래스명 맨앞글자를 소문자로)

@ModelAttribute는 생략이 가능이 가능하므로, 붙이거나 생략하거나 두가지 형태를 많이 보일것 같다.

댓글