[Spring] 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 강의 정리 - 9

JJAM·2022년 10월 5일
0
post-thumbnail

📖 스프링 MVC - 웹 페이지 만들기

📒 상품 등록 폼

전 게시물에서 만든 상품 목록, 상품 상세 처럼 컨트롤러를 생성해 준다.

✏️ 상품 등록 폼 - 컨트롤러

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이며
메시지 바디쿼리 파리미터 형식으로 전달된다.

✏️ 상품 등록 처리 - @RequestParam

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

@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}"; // 상품 상세로 이동
}

상품 수정 폼 페이지에서 값을 수정하고 저장 버튼을 누르면 정상적으로 상품이 수정된 것을 볼 수 있다.

📒 PRG Post/Redirect/Get

지금까지 진행한 프로젝트에는 큰 문제점이 있다.

바로 상품 등록 폼에서 상품 등록 버튼을 누르고 새로고침을 여러번 하면, 계속 상품 등록이 되는 것을 볼 수 있다.

왜냐하면 웹 브라우저에서 새로고침
마지막에 서버에 전송한 데이터를 다시 전송하는 일이기 때문이다.

상품 등록 버튼을 누르면 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} 가 호출되는 것을 볼 수 있다.

📒 RedirectAttributes

위에 리다이렉트를 하면서 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 에 대해 공부하였다.

profile
☘️

0개의 댓글