상품 수정 폼 컨트롤러
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는 생략이 가능이 가능하므로, 붙이거나 생략하거나 두가지 형태를 많이 보일것 같다.
'인프런 > 스프링 MVC 1편' 카테고리의 다른 글
18)타임리프 적용, @ModelAttribute의 추가 기능 (0) | 2023.01.29 |
---|---|
17)상품 도메인 개발,부트스트랩,타임리프 적용 (0) | 2023.01.27 |
16)스프링 MVC(웹페이지 만들기,웹 퍼블리셔,웹 프론트앤드) (0) | 2023.01.27 |
15)HTTP 메시지 컨버터 , RequestMappingHandlerAdapter 구조 (0) | 2023.01.26 |
14)HTTP 응답 문자(v1~v3) JSON(v1~v2) ,@RestController (0) | 2023.01.26 |
댓글