- 해당 게시물은 인프런 - "스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술" 강의를 참고하여 작성한 글 입니다.
- 유료강의이므로 자세한 내용은 없고, 간단한 설명 위주로 정리했습니다.
강의 링크 -> 김영한 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(유료강의)
전 게시물에서 만든 상품 목록, 상품 상세 처럼 컨트롤러와 뷰를 생성해 준다.
main/java/hello/itemservice/web/item/basic/BasicItemController
상품 등록 폼 페이지의 컨트롤러를 생성한다.
@GetMapping("/add")
public String addForm() {
// "basic/addForm" 라는 뷰 템플릿 호출
}
/resources/static/addForm.html
에서 만든 정적 html을
/resources/templates/basic/addForm.html
에서 뷰 템플릿(templates)으로 수정해준다.
폼 제출을 /basic/items/add 로 POST 방식으로 보낸다.
...
<form action="item.html" th:action method="post">
...
<button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='items.html'"
th:onclick="|location.href='@{/basic/items}'|"
type="button">취소
</button>
...
http://localhost:8080/basic/items/add 에 들어가면 정상적으로 상품 등록 폼 페이지가 뜨고,
취소 버튼을 누르면 상품 목록 페이지로 돌아가며, 제출 버튼은 아직 구현하지 않았다.
상품 등록 폼에서 전달된 데이터로 실제 상품을 등록 처리할 것이다.
이는 content-type: application/x-www-form-urlencoded
이며
메시지 바디에 쿼리 파리미터 형식으로 전달된다.
main/java/hello/itemservice/web/item/basic/BasicItemController
@RequestParam 애노테이션을 사용해서
요청 파라미터를 변수에 담아 상품 등록 처리할 수 있다.
@PostMapping("/add")
public String addItemV1(@RequestParam String itemName,
@RequestParam int price,
@RequestParam Integer quantity,
Model model) {
// item 객체 생성
Item item = new Item();
// item 객체에 파라미터 값 넣기
item.setItemName(itemName);
item.setPrice(price);
item.setQuantity(quantity);
// item 객체를 저장소에 저장
itemRepository.save(item);
// model에 item 객체 넣기
model.addAttribute("item", item);
// "basic/item" 뷰 템플릿 호출
return "basic/item";
}
하지만 강의 정리 - 6 게시물에서 @ModelAttribute를 사용하면 자동적으로 객체를 생성하고, 객체에 값을 넣어준다고 하였다.
그래서 좀 더 쉽게 사용하기 위해 @ModelAttribute를 사용해준다.
@ModelAttribute를 사용하면,
Item 객체를 생성하고 요청 파라미터의 값을 프로퍼티 접근법(setXxx)으로 입력해준다.
그리고 추가적으로 model에 해당 객체를 "item"이라는 이름으로 넣어준다.
@PostMapping("/add")
public String addItemV2(@ModelAttribute("item") Item item) {
itemRepository.save(item);
return "basic/item";
}
사람의 욕심은 끝이 없다고, 여기서 또 코드를 간결하게 만들 수 있다.
@ModelAttribute 의 이름을 생략할 수 있으며,
model에 저장될 때엔 @ModelAttribute의 클래스명인 Item
의 첫글자만 소문자로 변경되어 item
이라는 이름으로 저장된다.
@PostMapping("/add")
public String addItemV3(@ModelAttribute Item item) {
더 간결하게 @ModelAttribute 자체도 생략할 수 있다.
@PostMapping("/add")
public String addItemV4(Item item) {
상품 등록 폼에서 폼을 작성하고 상품 등록 버튼을 누르면 정상적으로 동작하는 것을 볼 수 있다.
main/java/hello/itemservice/web/item/basic/BasicItemController
상품 수정 폼 페이지의 컨트롤러를 생성한다.
@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model) {
// itemId를 통해 저장소에서 item 찾기
// 찾은 item을 "item"이름으로 model에 저장
// "basic/editForm" 뷰 템플릿 호출
}
/resources/static/editForm.html
에서 만든 정적 html을
/resources/templates/basic/editForm.html
에서 뷰 템플릿(templates)으로 수정해준다.
폼 제출을 /basic/items/{itemId}/edit 으로 POST 방식으로 보내며, 위에 작성한 상품 등록 폼과 비슷하다.
...
<form action="item.html" th:action method="post">
...
상품 상세 페이지에서 상품 수정 버튼을 누르면 다음과 같이 화면이 정상적으로 뜨는 것을 볼 수 있다.
상품 수정 페이지에서 저장 버튼을 눌렀을 때,
정상적으로 상품 수정 처리가 되도록 위에서 만든 상품 등록 처리와 비슷하게 만들 것이다.
main/java/hello/itemservice/web/item/basic/BasicItemController
@ModelAttribute를 사용하여 자동적으로
item 객체를 생성하고 파라미터 값들을 넣어주며, model에 item 객체를 넣어준다.
@PathVariable로 URL의 itemId값을 가져와 해당 itemId의 데이터들을 item 객체의 내용으로 update해준다.
마지막에 뷰 템플릿을 호출하는 대신에 상품 상세 화면으로 이동하도록 리다이렉트를 호출한다.
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {
itemRepository.update(itemId,item);
return "redirect:/basic/items/{itemId}"; // 상품 상세로 이동
}
상품 수정 폼 페이지에서 값을 수정하고 저장 버튼을 누르면 정상적으로 상품이 수정된 것을 볼 수 있다.
지금까지 진행한 프로젝트에는 큰 문제점이 있다.
바로 상품 등록 폼에서 상품 등록 버튼을 누르고 새로고침을 여러번 하면, 계속 상품 등록이 되는 것을 볼 수 있다.
왜냐하면 웹 브라우저에서 새로고침은
마지막에 서버에 전송한 데이터를 다시 전송하는 일이기 때문이다.
상품 등록 버튼을 누르면 POST 방식의 /add(@PostMapping("/add")
)가 서버로 상품 데이터를 전송한다.
그러고 새로고침을 하면 또 POST 방식의 /add가 서버로 또 전달이 되어,
상품 ID만 증가하고 내용이 같은 상품 데이터만 쌓이게 된다.
그래서 새로고침을 누르면 아래 사진 처럼 또 POST /add가 요청되는 것을 볼 수 있다.
이러한 상황을 해결하기 위해 Redirect
를 사용한다.
그러면 상품 저장 후, 상품 상세 화면으로 리다이렉트 호출되어
실제 상품 상세 화면으로 다시 이동된다.(GET /items/{id})
이후 새로 고침을 해도 GET /items/{id} 가 호출되므로 새로고침 문제를 해결할 수 있게 된다.
main/java/hello/itemservice/web/item/basic/BasicItemController
상품 등록 처리 컨트롤러의 return을 뷰 템플릿 호출이 아니라, 리다이렉트를 호출해주면 된다.
return "redirect:/basic/items/" + item.getId();
그래서 새로고침을 하면 POST /add 가 아니라 GET /items/{id} 가 호출되는 것을 볼 수 있다.
위에 리다이렉트를 하면서 URL에 + item.getId()
처럼 변수를 더하는 것은 위험하며,
고객 입장에서 제대로 저장이 되었는지 확신이 들지 않을 수 있어, 저장 후 "상품이 저장되었습니다" 라는 메시지를 보여줄 것이다.
main/java/hello/itemservice/web/item/basic/BasicItemController
RedirectAttributes 를 추가하여,
"itemId" 라는 이름으로 item의 id를 저장하고, "status" 라는 이름으로 true를 저장한다.
그러면 리다이렉트 할 때, redirectAttributes 에 넣은 itemId 값으로 치환 할 수 있게 된다.
@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}";
}
그래서 상품 등록을 하면 아래와 같이 item의 id가 치환이 된 것을 볼 수 있고,
쿼리 파라미터로 status=true
가 추가된 것을 볼 수 있다.
바로 redirectAttributes에서 pathVariable로 바인딩되지 않은 것을 쿼리 파라미터로 처리해주기 때문이다.
그래서 쿼리 파라미터 값인 status를 통해 상품 등록 했을 때,
"상품이 저장되었습니다." 라는 문구를 추가해 줄 수 있게 되었다.
/resources/templates/basic/item.html
<h2 th:if="${param.status}" th:text="'상품이 저장되었습니다.'"></h2>
지금까지 김영한 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(유료강의) 강의를 참고하여 스프링 MVC - 웹 페이지 만들기 2 에 대해 공부하였다.