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